diff options
author | David Bokan <bokan@chromium.org> | 2020-04-11 22:57:50 +0000 |
---|---|---|
committer | Michael BrĂ¼ning <michael.bruning@qt.io> | 2020-07-23 08:17:03 +0000 |
commit | 99fe8bdb44af780ea30c33839b6eebde4f4e2fe7 (patch) | |
tree | 23a6c7ed6c5055942cd7e8de4cc8a5d145056baf | |
parent | 7e405525e92dc4f77c59c891e9add4bb5f120fe9 (diff) | |
download | qtwebengine-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>
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( |