summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Bokan <bokan@chromium.org>2020-04-11 22:57:50 +0000
committerMichael BrĂ¼ning <michael.bruning@qt.io>2020-07-23 08:17:03 +0000
commit99fe8bdb44af780ea30c33839b6eebde4f4e2fe7 (patch)
tree23a6c7ed6c5055942cd7e8de4cc8a5d145056baf
parent7e405525e92dc4f77c59c891e9add4bb5f120fe9 (diff)
downloadqtwebengine-chromium-99fe8bdb44af780ea30c33839b6eebde4f4e2fe7.tar.gz
[Backport] CVE-2020-6531: Side-channel information leakage in scroll to text
Manual backport of patch originally reviewed on https://chromium-review.googlesource.com/c/chromium/src/+/2135407: Fix text fragment for user activation For security reasons, text fragments must only be activated when navigated with a user gesture. However, browser initiated navigations (e.g. user typing in the omnibox, bookmarks) don't have the user gesture bit set despite being initiated by the user (see discussion in https://crrev.com/c/2132673 for details). Because of this limitation, text fragment code explicitly checked if the navigation was browser initiated, assuming that such navigations are always user activated. However, history navigations are a special case. They're intentionally considered to be browser initiated, even if they originate from renderer script (e.g. `history.back()`). This meant that our check above would allow script to use the history API to activate a text fragment without a user gesture. This CL explicitly forbids activating a text fragment if the navigation is of history type. This is a trivial change (in terms of UX) because a history navigation will restore the scroll position to where the user left off so the text fragment scroll is already clobbered. This change prevents a transient scroll that will be undone. Note: we had an explicit test for this case that failed to catch the failure. The reason was that the test was checking that the fragment wasn't activated by checking that the scroll offset after a navigation is 0. However, the text fragment's scroll would be clobbered (assuming by history scroll restoration) so this check would erroneously pass. We fix it in this CL by using a scroll listener so that we can tell a scroll occurred even if it is later restored. Bug: 1042986 Change-Id: Ia0ad9a8adcda2250603e6a7dd2b386193be2a6e6 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r--chromium/content/browser/frame_host/navigation_request.h4
-rw-r--r--chromium/third_party/blink/renderer/core/loader/document_loader.h3
-rw-r--r--chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc36
-rw-r--r--chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc6
4 files changed, 41 insertions, 8 deletions
diff --git a/chromium/content/browser/frame_host/navigation_request.h b/chromium/content/browser/frame_host/navigation_request.h
index 9cd79b24e5c..d462cfe76a3 100644
--- a/chromium/content/browser/frame_host/navigation_request.h
+++ b/chromium/content/browser/frame_host/navigation_request.h
@@ -845,7 +845,9 @@ class CONTENT_EXPORT NavigationRequest
// be set in CreatedNavigationRequest.
// Note: |browser_initiated_| and |common_params_| may be mutated by
// ContentBrowserClient::OverrideNavigationParams at StartNavigation time
- // (i.e. before we actually kick off the navigation).
+ // (i.e. before we actually kick off the navigation). |browser_initiated|
+ // will always be true for history navigations, even if they began in the
+ // renderer using the history API.
mojom::CommonNavigationParamsPtr common_params_;
mojom::BeginNavigationParamsPtr begin_params_;
mojom::CommitNavigationParamsPtr commit_params_;
diff --git a/chromium/third_party/blink/renderer/core/loader/document_loader.h b/chromium/third_party/blink/renderer/core/loader/document_loader.h
index be87ae25946..bc962bc5c62 100644
--- a/chromium/third_party/blink/renderer/core/loader/document_loader.h
+++ b/chromium/third_party/blink/renderer/core/loader/document_loader.h
@@ -292,6 +292,9 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
bool HadTransientActivation() const { return had_transient_activation_; }
+ // Whether the navigation originated from the browser process. Note: history
+ // navigation is always considered to be browser initiated, even if the
+ // navigation was started using the history API in the renderer.
bool IsBrowserInitiated() const { return is_browser_initiated_; }
bool IsSameOriginNavigation() const { return is_same_origin_navigation_; }
diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
index 2a6feeafa08..dd8f8d0c85d 100644
--- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
+++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
@@ -55,12 +55,25 @@ bool ParseTextDirective(const String& fragment,
bool CheckSecurityRestrictions(LocalFrame& frame,
bool same_document_navigation) {
// This algorithm checks the security restrictions detailed in
- // https://wicg.github.io/ScrollToTextFragment/#should-allow-text-fragment
-
- // We only allow text fragment anchors for user or browser initiated
- // navigations, i.e. no script navigations.
- if (!(frame.Loader().GetDocumentLoader()->HadTransientActivation() ||
- frame.Loader().GetDocumentLoader()->IsBrowserInitiated())) {
+ // https://wicg.github.io/ScrollToTextFragment/#should-allow-a-text-fragment
+ // TODO(bokan): These are really only relevant for observable actions like
+ // scrolling. We should consider allowing highlighting regardless of these
+ // conditions. See the TODO in the relevant spec section:
+ // https://wicg.github.io/ScrollToTextFragment/#restricting-the-text-fragment
+
+ // History navigation is special because it's considered to be browser
+ // initiated even if the navigation originated via use of the history API
+ // within the renderer. We avoid creating a text fragment for history
+ // navigations since history scroll restoration should take precedence but
+ // it'd be bad if we ever got here for a history navigation since the check
+ // below would pass even if the user took no action.
+ SECURITY_CHECK(frame.Loader().GetDocumentLoader()->GetNavigationType() !=
+ kWebNavigationTypeBackForward);
+
+ // We only allow text fragment anchors for user navigations, e.g. link
+ // clicks, omnibox navigations, no script navigations.
+ if (!frame.Loader().GetDocumentLoader()->HadTransientActivation() &&
+ !frame.Loader().GetDocumentLoader()->IsBrowserInitiated()) {
return false;
}
@@ -96,6 +109,17 @@ TextFragmentAnchor* TextFragmentAnchor::TryCreateFragmentDirective(
if (!frame.GetDocument()->GetFragmentDirective())
return nullptr;
+ // Avoid invoking the text fragment for history or reload navigations as
+ // they'll be clobbered by scroll restoration; this prevents a transient
+ // scroll as well as user gesture issues; see https://crbug.com/1042986 for
+ // details.
+ auto navigation_type =
+ frame.Loader().GetDocumentLoader()->GetNavigationType();
+ if (navigation_type == kWebNavigationTypeBackForward ||
+ navigation_type == kWebNavigationTypeReload) {
+ return nullptr;
+ }
+
if (!CheckSecurityRestrictions(frame, same_document_navigation))
return nullptr;
diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
index aa00bcf0607..a4689fda786 100644
--- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
+++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
@@ -1544,7 +1544,11 @@ TEST_F(TextFragmentAnchorTest, TextDirectiveInSvg) {
}
// Ensure we restore the text highlight on page reload
-TEST_F(TextFragmentAnchorTest, HighlightOnReload) {
+// TODO(bokan): This test is disabled as this functionality was suppressed in
+// https://crrev.com/c/2135407; it would be better addressed by providing a
+// highlight-only function. See the TODO in
+// https://wicg.github.io/ScrollToTextFragment/#restricting-the-text-fragment
+TEST_F(TextFragmentAnchorTest, DISABLED_HighlightOnReload) {
SimRequest request("https://example.com/test.html#:~:text=test", "text/html");
LoadURL("https://example.com/test.html#:~:text=test");
const String& html = R"HTML(