backport_1042986.patch 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
  2. From: Cheng Zhao <[email protected]>
  3. Date: Thu, 4 Oct 2018 14:57:02 -0700
  4. Subject: fix: text fragment for user activation
  5. [1042986] [Low] [CVE-2020-6531]: iframe in victim page can detect Scroll To Text Fragment activation
  6. Backport https://chromium.googlesource.com/chromium/src/+/6ba752a6236a1adfff4b552bf141abd20f81fa4c
  7. diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
  8. index 5e47c91a5f5af59dfec21d4f1930d74cb05a55d9..f53b4c900f0e7786dcdb0824a34bf3720b301538 100644
  9. --- a/content/browser/frame_host/navigation_request.h
  10. +++ b/content/browser/frame_host/navigation_request.h
  11. @@ -901,7 +901,9 @@ class CONTENT_EXPORT NavigationRequest
  12. // be set in CreatedNavigationRequest.
  13. // Note: |browser_initiated_| and |common_params_| may be mutated by
  14. // ContentBrowserClient::OverrideNavigationParams at StartNavigation time
  15. - // (i.e. before we actually kick off the navigation).
  16. + // (i.e. before we actually kick off the navigation). |browser_initiated|
  17. + // will always be true for history navigations, even if they began in the
  18. + // renderer using the history API.
  19. mojom::CommonNavigationParamsPtr common_params_;
  20. mojom::BeginNavigationParamsPtr begin_params_;
  21. mojom::CommitNavigationParamsPtr commit_params_;
  22. diff --git a/content/browser/navigation_browsertest.cc b/content/browser/navigation_browsertest.cc
  23. index 0a8a18d178ded19600c33ca086d1c79fe30fa717..daffcb50bd9588e7b03d12cd245b6f8a3e9b065d 100644
  24. --- a/content/browser/navigation_browsertest.cc
  25. +++ b/content/browser/navigation_browsertest.cc
  26. @@ -2023,7 +2023,8 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledOnUserNavigation) {
  27. WaitForPageLoad(main_contents);
  28. frame_observer.WaitForScrollOffsetAtTop(
  29. /*expected_scroll_offset_at_top=*/false);
  30. - EXPECT_FALSE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  31. + RunUntilInputProcessed(GetWidgetHost());
  32. + EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
  33. }
  34. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  35. @@ -2039,7 +2040,8 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  36. WaitForPageLoad(main_contents);
  37. frame_observer.WaitForScrollOffsetAtTop(
  38. /*expected_scroll_offset_at_top=*/false);
  39. - EXPECT_FALSE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  40. + RunUntilInputProcessed(GetWidgetHost());
  41. + EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
  42. }
  43. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  44. @@ -2064,7 +2066,8 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  45. WaitForPageLoad(main_contents);
  46. frame_observer.WaitForScrollOffsetAtTop(
  47. /*expected_scroll_offset_at_top=*/false);
  48. - EXPECT_FALSE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  49. + RunUntilInputProcessed(GetWidgetHost());
  50. + EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
  51. }
  52. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  53. @@ -2091,7 +2094,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  54. FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
  55. run_loop.Run();
  56. RunUntilInputProcessed(GetWidgetHost());
  57. - EXPECT_TRUE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  58. + EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
  59. }
  60. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  61. @@ -2127,7 +2130,13 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  62. FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
  63. run_loop.Run();
  64. RunUntilInputProcessed(GetWidgetHost());
  65. - EXPECT_TRUE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  66. +
  67. + // Note: we use a scroll handler in the page to check whether any scrolls
  68. + // happened at all, rather than checking the current scroll offset. This is
  69. + // to ensure that if the offset is reset back to the top for other reasons
  70. + // (e.g. history restoration) we still fail this test. See
  71. + // https://crbug.com/1042986 for why this matters.
  72. + EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
  73. }
  74. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  75. @@ -2142,11 +2151,13 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  76. WaitForPageLoad(main_contents);
  77. frame_observer.WaitForScrollOffsetAtTop(false);
  78. - EXPECT_FALSE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  79. - // Scroll the page back to top.
  80. + // Scroll the page back to top. Make sure we reset the |did_scroll| variable
  81. + // we'll use below to ensure the same-document navigation invokes the text
  82. + // fragment.
  83. EXPECT_TRUE(ExecuteScript(main_contents, "window.scrollTo(0, 0)"));
  84. frame_observer.WaitForScrollOffsetAtTop(true);
  85. + EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;"));
  86. // Perform a same-document browser initiated navigation
  87. GURL same_doc_url(embedded_test_server()->GetURL(
  88. @@ -2156,7 +2167,8 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  89. WaitForPageLoad(main_contents);
  90. frame_observer.WaitForScrollOffsetAtTop(
  91. /*expected_scroll_offset_at_top=*/false);
  92. - EXPECT_FALSE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  93. + RunUntilInputProcessed(GetWidgetHost());
  94. + EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
  95. }
  96. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  97. @@ -2184,7 +2196,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  98. FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
  99. run_loop.Run();
  100. RunUntilInputProcessed(GetWidgetHost());
  101. - EXPECT_TRUE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  102. + EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
  103. }
  104. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
  105. @@ -2211,6 +2223,12 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
  106. "Content-Type: text/html; charset=utf-8\r\n"
  107. "Document-Policy: no-force-load-at-top\r\n"
  108. "\r\n"
  109. + "<script>"
  110. + " let did_scroll = false;"
  111. + " window.addEventListener('scroll', () => {"
  112. + " did_scroll = true;"
  113. + " });"
  114. + "</script>"
  115. "<p style='position: absolute; top: 10000px;'>Some text</p>");
  116. response.Done();
  117. @@ -2221,7 +2239,8 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
  118. WaitForPageLoad(main_contents);
  119. frame_observer.WaitForScrollOffsetAtTop(
  120. /*expected_scroll_offset_at_top=*/false);
  121. - EXPECT_FALSE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  122. + RunUntilInputProcessed(GetWidgetHost());
  123. + EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
  124. }
  125. IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  126. @@ -2248,6 +2267,12 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  127. "Content-Type: text/html; charset=utf-8\r\n"
  128. "Document-Policy: force-load-at-top\r\n"
  129. "\r\n"
  130. + "<script>"
  131. + " let did_scroll = false;"
  132. + " window.addEventListener('scroll', () => {"
  133. + " did_scroll = true;"
  134. + " });"
  135. + "</script>"
  136. "<p style='position: absolute; top: 10000px;'>Some text</p>");
  137. response.Done();
  138. @@ -2262,7 +2287,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
  139. FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
  140. run_loop.Run();
  141. RunUntilInputProcessed(GetWidgetHost());
  142. - EXPECT_TRUE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
  143. + EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
  144. }
  145. // Regression test for https://crbug.com/996044
  146. diff --git a/content/test/data/scrollable_page_with_content.html b/content/test/data/scrollable_page_with_content.html
  147. index c37d095d22231d12faa19985e8e98b3f9368fab1..1ef150d3a4289caef3a883c92feb79216f77f457 100644
  148. --- a/content/test/data/scrollable_page_with_content.html
  149. +++ b/content/test/data/scrollable_page_with_content.html
  150. @@ -1,6 +1,12 @@
  151. <html>
  152. <head>
  153. <meta name="viewport" content="width=device-width, minimum-scale=1.0">
  154. + <script>
  155. + let did_scroll = false;
  156. + window.addEventListener('scroll', () => {
  157. + did_scroll = true;
  158. + });
  159. + </script>
  160. <style>
  161. p {
  162. position: absolute;
  163. diff --git a/content/test/data/target_text_link.html b/content/test/data/target_text_link.html
  164. index 00f40bf042aed3476f07a9cc0575159c52cba9f2..ade7e42029f40b213c9110dd88ac270edb8d26f3 100644
  165. --- a/content/test/data/target_text_link.html
  166. +++ b/content/test/data/target_text_link.html
  167. @@ -5,4 +5,4 @@
  168. <body>
  169. <a id="link" href="/scrollable_page_with_content.html#:~:text=text">link</a>
  170. </body>
  171. -</html>>
  172. +</html>
  173. diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
  174. index 31fc5754a02318ab5eb7b239cea1530baf80ecba..cbf0e87fd81e48f77dac052a7f5992cf0dbaa2e0 100644
  175. --- a/third_party/blink/renderer/core/loader/document_loader.h
  176. +++ b/third_party/blink/renderer/core/loader/document_loader.h
  177. @@ -294,6 +294,9 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
  178. bool HadTransientActivation() const { return had_transient_activation_; }
  179. + // Whether the navigation originated from the browser process. Note: history
  180. + // navigation is always considered to be browser initiated, even if the
  181. + // navigation was started using the history API in the renderer.
  182. bool IsBrowserInitiated() const { return is_browser_initiated_; }
  183. bool IsSameOriginNavigation() const { return is_same_origin_navigation_; }
  184. diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
  185. index ebdb5b14f8a36ee44f6f80e57c3221ac79f566e7..cc7cde84dcfd8543e7cc0bb5048c5225c13703e9 100644
  186. --- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
  187. +++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
  188. @@ -61,12 +61,25 @@ bool ParseTextDirective(const String& fragment,
  189. bool CheckSecurityRestrictions(LocalFrame& frame,
  190. bool same_document_navigation) {
  191. // This algorithm checks the security restrictions detailed in
  192. - // https://wicg.github.io/ScrollToTextFragment/#should-allow-text-fragment
  193. -
  194. - // We only allow text fragment anchors for user or browser initiated
  195. - // navigations, i.e. no script navigations.
  196. - if (!(frame.Loader().GetDocumentLoader()->HadTransientActivation() ||
  197. - frame.Loader().GetDocumentLoader()->IsBrowserInitiated())) {
  198. + // https://wicg.github.io/ScrollToTextFragment/#should-allow-a-text-fragment
  199. + // TODO(bokan): These are really only relevant for observable actions like
  200. + // scrolling. We should consider allowing highlighting regardless of these
  201. + // conditions. See the TODO in the relevant spec section:
  202. + // https://wicg.github.io/ScrollToTextFragment/#restricting-the-text-fragment
  203. +
  204. + // History navigation is special because it's considered to be browser
  205. + // initiated even if the navigation originated via use of the history API
  206. + // within the renderer. We avoid creating a text fragment for history
  207. + // navigations since history scroll restoration should take precedence but
  208. + // it'd be bad if we ever got here for a history navigation since the check
  209. + // below would pass even if the user took no action.
  210. + SECURITY_CHECK(frame.Loader().GetDocumentLoader()->GetNavigationType() !=
  211. + kWebNavigationTypeBackForward);
  212. +
  213. + // We only allow text fragment anchors for user navigations, e.g. link
  214. + // clicks, omnibox navigations, no script navigations.
  215. + if (!frame.Loader().GetDocumentLoader()->HadTransientActivation() &&
  216. + !frame.Loader().GetDocumentLoader()->IsBrowserInitiated()) {
  217. return false;
  218. }
  219. @@ -104,6 +117,17 @@ TextFragmentAnchor* TextFragmentAnchor::TryCreateFragmentDirective(
  220. if (!frame.GetDocument()->GetFragmentDirective())
  221. return nullptr;
  222. + // Avoid invoking the text fragment for history or reload navigations as
  223. + // they'll be clobbered by scroll restoration; this prevents a transient
  224. + // scroll as well as user gesture issues; see https://crbug.com/1042986 for
  225. + // details.
  226. + auto navigation_type =
  227. + frame.Loader().GetDocumentLoader()->GetNavigationType();
  228. + if (navigation_type == kWebNavigationTypeBackForward ||
  229. + navigation_type == kWebNavigationTypeReload) {
  230. + return nullptr;
  231. + }
  232. +
  233. if (!CheckSecurityRestrictions(frame, same_document_navigation))
  234. return nullptr;
  235. diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
  236. index c8c87c95319a63d93a90123a4150cb3a4b2f95bf..258b9ac343a8fbcec635ddb6103cc485d540b50e 100644
  237. --- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
  238. +++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
  239. @@ -1611,7 +1611,11 @@ TEST_F(TextFragmentAnchorTest, TextDirectiveInSvg) {
  240. }
  241. // Ensure we restore the text highlight on page reload
  242. -TEST_F(TextFragmentAnchorTest, HighlightOnReload) {
  243. +// TODO(bokan): This test is disabled as this functionality was suppressed in
  244. +// https://crrev.com/c/2135407; it would be better addressed by providing a
  245. +// highlight-only function. See the TODO in
  246. +// https://wicg.github.io/ScrollToTextFragment/#restricting-the-text-fragment
  247. +TEST_F(TextFragmentAnchorTest, DISABLED_HighlightOnReload) {
  248. SimRequest request("https://example.com/test.html#:~:text=test", "text/html");
  249. LoadURL("https://example.com/test.html#:~:text=test");
  250. const String& html = R"HTML(