summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiam Brady <lbrady@google.com>2022-10-21 23:50:32 +0200
committerMichael BrĂ¼ning <michael.bruning@qt.io>2022-12-06 15:46:09 +0000
commit8dc71a1364d998f9d93ff995e2dd32a0ba4ed371 (patch)
treea521bc31ce27589abe3b1f85420a094d7de979dd
parente3bbb5e8fa52c910b959bc8619ddddabc3cc0603 (diff)
downloadqtwebengine-chromium-8dc71a1364d998f9d93ff995e2dd32a0ba4ed371.tar.gz
[Backport] CVE-2022-4182: Inappropriate implementation in Fenced Frames
Manual backport of patch originally reviewed on https://chromium-review.googlesource.com/c/chromium/src/+/3971895: Fenced frame: have anchor focus require user activation Currently, focus can be pulled across a fenced frame boundary without any user activation by using anchor fragments (aka setting location.href to a.com#anchor). We already have script-based focus properly gated, but this is a corner case that we missed. This CL adds a new variable to FocusParams(): `gate_on_user_activation`. If set to true, then focus that crosses a fenced frame boundary will only be allowed to happen if the target frame has transient user activation. This check takes place in `Frame::AllowFocusWithoutUserActivation()`. This CL also updates the Focus() call in `ElementFragmentAnchor::ApplyFocusIfNeeded` to set `gate_on_user_activation` to true. This has the effect of treating anchor focusing as a programmatic focus. However, there isn't a legitimate use case where a fenced frame will need to pull focus into itself without user gesture using anchor focusing, and, the behavior will remain unchanged for anchor focusing that does not cross a fenced frame boundary. So, it's okay to add this restriction. (cherry picked from commit 855a43d7acc395d80c3932d382061ade2c82626e) Bug: 1368739 Change-Id: Ia25e96e23e19d780ac8a4c8edb60c0b2472a9e18 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3933078 Reviewed-by: Dave Tapuska <dtapuska@chromium.org> Commit-Queue: Liam Brady <lbrady@google.com> Cr-Original-Commit-Position: refs/heads/main@{#1061827} Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3971895 Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> Reviewed-by: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/branch-heads/5359@{#196} Cr-Branched-From: 27d3765d341b09369006d030f83f582a29eb57ae-refs/heads/main@{#1058933} Reviewed-on: https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/446488 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r--chromium/third_party/blink/renderer/core/dom/element.cc69
-rw-r--r--chromium/third_party/blink/renderer/core/dom/element.h1
-rw-r--r--chromium/third_party/blink/renderer/core/dom/focus_params.h10
-rw-r--r--chromium/third_party/blink/renderer/core/frame/dom_window.cc2
-rw-r--r--chromium/third_party/blink/renderer/core/frame/frame.cc2
-rw-r--r--chromium/third_party/blink/renderer/core/frame/frame.h10
-rw-r--r--chromium/third_party/blink/renderer/core/page/focus_controller.cc30
-rw-r--r--chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc3
8 files changed, 70 insertions, 57 deletions
diff --git a/chromium/third_party/blink/renderer/core/dom/element.cc b/chromium/third_party/blink/renderer/core/dom/element.cc
index bfbf6b3c103..44d3a815d26 100644
--- a/chromium/third_party/blink/renderer/core/dom/element.cc
+++ b/chromium/third_party/blink/renderer/core/dom/element.cc
@@ -4905,14 +4905,11 @@ Element* Element::GetAutofocusDelegate() const {
return nullptr;
}
-void Element::focusForBindings() {
- focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
- mojom::blink::FocusType::kScript, nullptr));
-}
-
void Element::focusForBindings(const FocusOptions* options) {
focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
- mojom::blink::FocusType::kScript, nullptr, options));
+ mojom::blink::FocusType::kScript,
+ /*capabilities=*/nullptr, options,
+ /*gate_on_user_activation=*/true));
}
void Element::focus() {
@@ -4921,7 +4918,8 @@ void Element::focus() {
void Element::focus(const FocusOptions* options) {
focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
- mojom::blink::FocusType::kNone, nullptr, options));
+ mojom::blink::FocusType::kNone, /*capabilities=*/nullptr,
+ options));
}
void Element::focus(const FocusParams& params) {
@@ -4952,6 +4950,37 @@ void Element::focus(const FocusParams& params) {
return;
}
+ FocusOptions* focus_options = nullptr;
+ if (params.gate_on_user_activation) {
+ LocalFrame& frame = *GetDocument().GetFrame();
+ if (!frame.AllowFocusWithoutUserActivation() &&
+ !LocalFrame::HasTransientUserActivation(&frame)) {
+ return;
+ }
+
+ // Fenced frame focusing should not auto-scroll, since that behavior can
+ // be observed by an embedder.
+ if (frame.IsInFencedFrameTree()) {
+ focus_options = FocusOptions::Create();
+ focus_options->setPreventScroll(true);
+ }
+
+ // Fenced frames should consume user activation when attempting to pull
+ // focus across a fenced boundary into itself.
+ // TODO(crbug.com/848778) Right now the browser can't verify that the
+ // renderer properly consumed user activation. When user activation code is
+ // migrated to the browser, move this logic to the browser as well.
+ if (!frame.AllowFocusWithoutUserActivation() &&
+ frame.IsInFencedFrameTree()) {
+ LocalFrame::ConsumeTransientUserActivation(&frame);
+ }
+ }
+
+ FocusParams params_to_use = FocusParams(
+ params.selection_behavior, params.type, params.source_capabilities,
+ focus_options ? focus_options : params.options,
+ params.gate_on_user_activation);
+
// Ensure we have clean style (including forced display locks).
GetDocument().UpdateStyleAndLayoutTreeForNode(this);
@@ -4962,9 +4991,9 @@ void Element::focus(const FocusParams& params) {
if (Element* new_focus_target = GetFocusableArea()) {
// Unlike the specification, we re-run focus() for new_focus_target
// because we can't change |this| in a member function.
- new_focus_target->focus(FocusParams(SelectionBehaviorOnFocus::kReset,
- mojom::blink::FocusType::kForward,
- nullptr, params.options));
+ new_focus_target->focus(FocusParams(
+ SelectionBehaviorOnFocus::kReset, mojom::blink::FocusType::kForward,
+ /*capabilities=*/nullptr, params_to_use.options));
}
// 2. If new focus target is null, then:
// 2.1. If no fallback target was specified, then return.
@@ -4973,12 +5002,13 @@ void Element::focus(const FocusParams& params) {
// If a script called focus(), then the type would be kScript. This means
// we are activating because of a script action (kScriptFocus). Otherwise,
// this is a user activation (kUserFocus).
- ActivateDisplayLockIfNeeded(params.type == mojom::blink::FocusType::kScript
+ ActivateDisplayLockIfNeeded(params_to_use.type ==
+ mojom::blink::FocusType::kScript
? DisplayLockActivationReason::kScriptFocus
: DisplayLockActivationReason::kUserFocus);
if (!GetDocument().GetPage()->GetFocusController().SetFocusedElement(
- this, GetDocument().GetFrame(), params))
+ this, GetDocument().GetFrame(), params_to_use))
return;
if (GetDocument().FocusedElement() == this) {
@@ -4999,14 +5029,15 @@ void Element::focus(const FocusParams& params) {
// Trigger a tooltip to show for the newly focused element only when the
// focus was set resulting from a keyboard action.
//
- // TODO(bebeaudr): To also trigger a tooltip when the |params.type| is
- // kSpatialNavigation, we'll first have to ensure that the fake mouse move
- // event fired by `SpatialNavigationController::DispatchMouseMoveEvent` does
- // not lead to a cursor triggered tooltip update. The only tooltip update
- // that there should be in that case is the one triggered from the spatial
- // navigation keypress. This issue is tracked in https://crbug.com/1206446.
+ // TODO(bebeaudr): To also trigger a tooltip when the |params_to_use.type|
+ // is kSpatialNavigation, we'll first have to ensure that the fake mouse
+ // move event fired by `SpatialNavigationController::DispatchMouseMoveEvent`
+ // does not lead to a cursor triggered tooltip update. The only tooltip
+ // update that there should be in that case is the one triggered from the
+ // spatial navigation keypress. This issue is tracked in
+ // https://crbug.com/1206446.
bool is_focused_from_keypress = false;
- switch (params.type) {
+ switch (params_to_use.type) {
case mojom::blink::FocusType::kScript:
if (GetDocument()
.GetFrame()
diff --git a/chromium/third_party/blink/renderer/core/dom/element.h b/chromium/third_party/blink/renderer/core/dom/element.h
index 045e5623a9a..50fe406b39f 100644
--- a/chromium/third_party/blink/renderer/core/dom/element.h
+++ b/chromium/third_party/blink/renderer/core/dom/element.h
@@ -722,7 +722,6 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable {
Element* GetFocusableArea() const;
Element* GetAutofocusDelegate() const;
// Element focus function called through IDL (i.e. element.focus() in JS)
- void focusForBindings();
void focusForBindings(const FocusOptions*);
// Element focus function called from outside IDL (user focus,
// accessibility, etc...)
diff --git a/chromium/third_party/blink/renderer/core/dom/focus_params.h b/chromium/third_party/blink/renderer/core/dom/focus_params.h
index c6245bcd9ea..670b422e32d 100644
--- a/chromium/third_party/blink/renderer/core/dom/focus_params.h
+++ b/chromium/third_party/blink/renderer/core/dom/focus_params.h
@@ -17,14 +17,19 @@ struct FocusParams {
public:
FocusParams() : options(FocusOptions::Create()) {}
+ explicit FocusParams(bool gate_on_user_activation)
+ : options(FocusOptions::Create()),
+ gate_on_user_activation(gate_on_user_activation) {}
FocusParams(SelectionBehaviorOnFocus selection,
mojom::blink::FocusType focus_type,
InputDeviceCapabilities* capabilities,
- const FocusOptions* focus_options = FocusOptions::Create())
+ const FocusOptions* focus_options = FocusOptions::Create(),
+ bool gate_on_user_activation = false)
: selection_behavior(selection),
type(focus_type),
source_capabilities(capabilities),
- options(focus_options) {}
+ options(focus_options),
+ gate_on_user_activation(gate_on_user_activation) {}
SelectionBehaviorOnFocus selection_behavior =
SelectionBehaviorOnFocus::kRestore;
@@ -32,6 +37,7 @@ struct FocusParams {
InputDeviceCapabilities* source_capabilities = nullptr;
const FocusOptions* options = nullptr;
bool omit_blur_events = false;
+ bool gate_on_user_activation = false;
};
} // namespace blink
diff --git a/chromium/third_party/blink/renderer/core/frame/dom_window.cc b/chromium/third_party/blink/renderer/core/frame/dom_window.cc
index 3f64d9aa75b..8d28001c82b 100644
--- a/chromium/third_party/blink/renderer/core/frame/dom_window.cc
+++ b/chromium/third_party/blink/renderer/core/frame/dom_window.cc
@@ -440,7 +440,7 @@ void DOMWindow::focus(v8::Isolate* isolate) {
if (!page)
return;
- if (!frame->ShouldAllowScriptFocus()) {
+ if (!frame->AllowFocusWithoutUserActivation()) {
// Disallow script focus that crosses a fenced frame boundary on a
// frame that doesn't have transient user activation. Note: all calls to
// DOMWindow::focus come from JavaScript calls in the web platform
diff --git a/chromium/third_party/blink/renderer/core/frame/frame.cc b/chromium/third_party/blink/renderer/core/frame/frame.cc
index a519c49a019..8821bea24bd 100644
--- a/chromium/third_party/blink/renderer/core/frame/frame.cc
+++ b/chromium/third_party/blink/renderer/core/frame/frame.cc
@@ -698,7 +698,7 @@ bool Frame::FocusCrossesFencedBoundary() {
return false;
}
-bool Frame::ShouldAllowScriptFocus() {
+bool Frame::AllowFocusWithoutUserActivation() {
if (!features::IsFencedFramesEnabled())
return true;
diff --git a/chromium/third_party/blink/renderer/core/frame/frame.h b/chromium/third_party/blink/renderer/core/frame/frame.h
index 9ca0647670f..c1a5f9bc17a 100644
--- a/chromium/third_party/blink/renderer/core/frame/frame.h
+++ b/chromium/third_party/blink/renderer/core/frame/frame.h
@@ -213,9 +213,13 @@ class CORE_EXPORT Frame : public GarbageCollected<Frame> {
void SetIsLoading(bool is_loading) { is_loading_ = is_loading; }
bool IsLoading() const { return is_loading_; }
- // Determines if the frame should be allowed to pull focus from a JavaScript
- // call.
- bool ShouldAllowScriptFocus();
+ // Determines if the frame should be allowed to pull focus without receiving
+ // user activation. A frame cannot pull focus without user activation if
+ // doing so would cross a fenced frame boundary.
+ // Note: the only time focus can be pulled across a fence without the target
+ // frame having user activation is in the case of tab-focusing. In that case,
+ // this function is not called and focus is blanket allowed.
+ bool AllowFocusWithoutUserActivation();
// Tells the frame to check whether its load has completed, based on the state
// of its subframes, etc.
diff --git a/chromium/third_party/blink/renderer/core/page/focus_controller.cc b/chromium/third_party/blink/renderer/core/page/focus_controller.cc
index f627814af46..66c720ea596 100644
--- a/chromium/third_party/blink/renderer/core/page/focus_controller.cc
+++ b/chromium/third_party/blink/renderer/core/page/focus_controller.cc
@@ -1296,34 +1296,6 @@ bool FocusController::SetFocusedElement(Element* element,
new_document->FocusedElement() == element)
return true;
- // Fenced frame focusing should not auto-scroll, since that behavior can
- // be observed by an embedder.
- FocusParams params_to_use = params;
- if (new_document && params.type == mojom::blink::FocusType::kScript &&
- new_document->GetFrame()->IsInFencedFrameTree()) {
- FocusOptions* focus_options = FocusOptions::Create();
- focus_options->setPreventScroll(true);
- params_to_use = FocusParams(params.selection_behavior, params.type,
- params.source_capabilities, focus_options);
- }
-
- if (new_focused_frame && !new_focused_frame->ShouldAllowScriptFocus() &&
- params_to_use.type == mojom::blink::FocusType::kScript) {
- // Disallow script focus that crosses a fenced frame boundary on a
- // frame that doesn't have transient user activation.
- if (!new_focused_frame->HasTransientUserActivation())
- return false;
- // Fenced frames should consume user activation when attempting to pull
- // focus across a fenced boundary into itself.
- // TODO(crbug.com/1123606) Right now the browser can't verify that the
- // renderer properly consumed user activation. When user activation code is
- // migrated to the browser, move this logic to the browser as well.
- if (new_focused_frame->IsInFencedFrameTree()) {
- LocalFrame::ConsumeTransientUserActivation(
- DynamicTo<LocalFrame>(new_focused_frame));
- }
- }
-
if (old_document && old_document != new_document)
old_document->ClearFocusedElement();
@@ -1336,7 +1308,7 @@ bool FocusController::SetFocusedElement(Element* element,
if (new_document) {
bool successfully_focused =
- new_document->SetFocusedElement(element, params_to_use);
+ new_document->SetFocusedElement(element, params);
if (!successfully_focused)
return false;
diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc b/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc
index a5bacdbe09e..443630ed601 100644
--- a/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc
+++ b/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc
@@ -10,6 +10,7 @@
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/focus_params.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -203,7 +204,7 @@ void ElementFragmentAnchor::ApplyFocusIfNeeded() {
// clear focus, which matches the behavior of other browsers.
auto* element = DynamicTo<Element>(anchor_node_.Get());
if (element && element->IsFocusable()) {
- element->focus();
+ element->focus(FocusParams(/*gate_on_user_activation=*/true));
} else {
frame_->GetDocument()->SetSequentialFocusNavigationStartingPoint(
anchor_node_);