1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <stdint.h>
6 
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/feature_list.h"
10 #include "base/files/file_util.h"
11 #include "base/macros.h"
12 #include "base/memory/ptr_util.h"
13 #include "base/optional.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/test/scoped_feature_list.h"
16 #include "build/build_config.h"
17 #include "content/browser/bad_message.h"
18 #include "content/browser/child_process_security_policy_impl.h"
19 #include "content/browser/dom_storage/dom_storage_context_wrapper.h"
20 #include "content/browser/dom_storage/session_storage_namespace_impl.h"
21 #include "content/browser/renderer_host/navigator.h"
22 #include "content/browser/renderer_host/render_frame_host_impl.h"
23 #include "content/browser/renderer_host/render_frame_proxy_host.h"
24 #include "content/browser/renderer_host/render_process_host_impl.h"
25 #include "content/browser/renderer_host/render_view_host_factory.h"
26 #include "content/browser/renderer_host/render_view_host_impl.h"
27 #include "content/browser/web_contents/file_chooser_impl.h"
28 #include "content/browser/web_contents/web_contents_impl.h"
29 #include "content/common/frame.mojom.h"
30 #include "content/common/frame_messages.h"
31 #include "content/common/frame_proxy.mojom-test-utils.h"
32 #include "content/common/render_message_filter.mojom.h"
33 #include "content/public/browser/blob_handle.h"
34 #include "content/public/browser/browser_context.h"
35 #include "content/public/browser/browser_task_traits.h"
36 #include "content/public/browser/browser_thread.h"
37 #include "content/public/browser/content_browser_client.h"
38 #include "content/public/browser/file_select_listener.h"
39 #include "content/public/browser/navigation_handle.h"
40 #include "content/public/browser/resource_context.h"
41 #include "content/public/browser/storage_partition.h"
42 #include "content/public/common/bindings_policy.h"
43 #include "content/public/common/content_features.h"
44 #include "content/public/common/content_switches.h"
45 #include "content/public/common/navigation_policy.h"
46 #include "content/public/common/url_constants.h"
47 #include "content/public/test/back_forward_cache_util.h"
48 #include "content/public/test/browser_test.h"
49 #include "content/public/test/browser_test_utils.h"
50 #include "content/public/test/content_browser_test.h"
51 #include "content/public/test/content_browser_test_utils.h"
52 #include "content/public/test/navigation_handle_observer.h"
53 #include "content/public/test/test_navigation_observer.h"
54 #include "content/public/test/test_renderer_host.h"
55 #include "content/public/test/test_utils.h"
56 #include "content/shell/browser/shell.h"
57 #include "content/test/content_browser_test_utils_internal.h"
58 #include "content/test/did_commit_navigation_interceptor.h"
59 #include "content/test/frame_host_interceptor.h"
60 #include "content/test/test_content_browser_client.h"
61 #include "ipc/ipc_message.h"
62 #include "ipc/ipc_security_test_util.h"
63 #include "mojo/core/embedder/embedder.h"
64 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
65 #include "mojo/public/cpp/bindings/pending_receiver.h"
66 #include "mojo/public/cpp/bindings/pending_remote.h"
67 #include "mojo/public/cpp/bindings/remote.h"
68 #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
69 #include "mojo/public/cpp/test_support/test_utils.h"
70 #include "net/base/filename_util.h"
71 #include "net/base/network_isolation_key.h"
72 #include "net/dns/mock_host_resolver.h"
73 #include "net/test/embedded_test_server/controllable_http_response.h"
74 #include "net/test/embedded_test_server/embedded_test_server.h"
75 #include "net/test/embedded_test_server/http_request.h"
76 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
77 #include "services/network/public/cpp/features.h"
78 #include "services/network/public/cpp/network_switches.h"
79 #include "services/network/public/cpp/resource_request.h"
80 #include "services/network/public/mojom/trust_tokens.mojom.h"
81 #include "services/network/public/mojom/url_loader.mojom.h"
82 #include "services/network/test/test_url_loader_client.h"
83 #include "storage/browser/blob/blob_registry_impl.h"
84 #include "testing/gmock/include/gmock/gmock.h"
85 #include "testing/gtest/include/gtest/gtest.h"
86 #include "third_party/blink/public/common/blob/blob_utils.h"
87 #include "third_party/blink/public/common/navigation/triggering_event_info.h"
88 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
89 #include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h"
90 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
91 #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h"
92 
93 using IPC::IpcSecurityTestUtil;
94 using ::testing::HasSubstr;
95 using ::testing::Optional;
96 
97 namespace content {
98 
99 namespace {
100 
101 // This is a helper function for the tests which attempt to create a
102 // duplicate RenderViewHost or RenderWidgetHost. It tries to create two objects
103 // with the same process and routing ids, which causes a collision.
104 // It creates a couple of windows in process 1, which causes a few routing ids
105 // to be allocated. Then a cross-process navigation is initiated, which causes a
106 // new process 2 to be created and have a pending RenderViewHost for it. The
107 // routing id of the RenderViewHost which is target for a duplicate is set
108 // into |target_routing_id| and the pending RenderFrameHost which is used for
109 // the attempt is the return value.
PrepareToDuplicateHosts(Shell * shell,net::EmbeddedTestServer * server,int * target_routing_id)110 RenderFrameHostImpl* PrepareToDuplicateHosts(Shell* shell,
111                                              net::EmbeddedTestServer* server,
112                                              int* target_routing_id) {
113   GURL foo("http://foo.com/simple_page.html");
114 
115   if (AreDefaultSiteInstancesEnabled()) {
116     // Isolate "bar.com" so we are guaranteed to get a different process
117     // for navigations to this origin.
118     IsolateOriginsForTesting(server, shell->web_contents(), {"bar.com"});
119   }
120 
121   // Start off with initial navigation, so we get the first process allocated.
122   EXPECT_TRUE(NavigateToURL(shell, foo));
123   EXPECT_EQ(base::ASCIIToUTF16("OK"), shell->web_contents()->GetTitle());
124 
125   // Open another window, so we generate some more routing ids.
126   ShellAddedObserver shell2_observer;
127   EXPECT_TRUE(ExecuteScript(shell, "window.open(document.URL + '#2');"));
128   Shell* shell2 = shell2_observer.GetShell();
129 
130   // The new window must be in the same process, but have a new routing id.
131   EXPECT_EQ(shell->web_contents()->GetMainFrame()->GetProcess()->GetID(),
132             shell2->web_contents()->GetMainFrame()->GetProcess()->GetID());
133   *target_routing_id =
134       shell2->web_contents()->GetRenderViewHost()->GetRoutingID();
135   EXPECT_NE(*target_routing_id,
136             shell->web_contents()->GetRenderViewHost()->GetRoutingID());
137 
138   // Now, simulate a link click coming from the renderer.
139   GURL extension_url("http://bar.com/simple_page.html");
140   WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell->web_contents());
141   wc->GetFrameTree()->root()->navigator().RequestOpenURL(
142       wc->GetFrameTree()->root()->current_frame_host(), extension_url,
143       GlobalFrameRoutingId() /* initiator_routing_id */,
144       url::Origin::Create(foo), nullptr, std::string(), Referrer(),
145       WindowOpenDisposition::CURRENT_TAB, false, true,
146       blink::TriggeringEventInfo::kFromTrustedEvent, std::string(),
147       nullptr /* blob_url_loader_factory */, base::nullopt /* impression */);
148 
149   // Since the navigation above requires a cross-process swap, there will be a
150   // speculative/pending RenderFrameHost. Ensure it exists and is in a different
151   // process than the initial page.
152   RenderFrameHostImpl* next_rfh =
153       wc->GetRenderManagerForTesting()->speculative_frame_host();
154 
155   EXPECT_TRUE(next_rfh);
156   EXPECT_NE(shell->web_contents()->GetMainFrame()->GetProcess()->GetID(),
157             next_rfh->GetProcess()->GetID());
158 
159   return next_rfh;
160 }
161 
CreateOpenURLParams(const GURL & url)162 content::mojom::OpenURLParamsPtr CreateOpenURLParams(const GURL& url) {
163   auto params = mojom::OpenURLParams::New();
164   params->url = url;
165   params->disposition = WindowOpenDisposition::CURRENT_TAB;
166   params->should_replace_current_entry = false;
167   params->user_gesture = true;
168   return params;
169 }
170 
CreateMemoryBackedBlob(BrowserContext * browser_context,const std::string & contents,const std::string & content_type)171 std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
172     BrowserContext* browser_context,
173     const std::string& contents,
174     const std::string& content_type) {
175   std::unique_ptr<content::BlobHandle> result;
176   base::RunLoop loop;
177   BrowserContext::CreateMemoryBackedBlob(
178       browser_context, base::as_bytes(base::make_span(contents)), content_type,
179       base::BindOnce(
180           [](std::unique_ptr<content::BlobHandle>* out_blob,
181              base::OnceClosure done,
182              std::unique_ptr<content::BlobHandle> blob) {
183             *out_blob = std::move(blob);
184             std::move(done).Run();
185           },
186           &result, loop.QuitClosure()));
187   loop.Run();
188   EXPECT_TRUE(result);
189   return result;
190 }
191 
192 // Helper class to interpose on Blob URL registrations, replacing the URL
193 // contained in incoming registration requests with the specified URL.
194 class BlobURLStoreInterceptor
195     : public blink::mojom::BlobURLStoreInterceptorForTesting {
196  public:
Intercept(GURL target_url,mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore> receiver)197   static void Intercept(
198       GURL target_url,
199       mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>
200           receiver) {
201     auto interceptor =
202         base::WrapUnique(new BlobURLStoreInterceptor(target_url));
203     auto* raw_interceptor = interceptor.get();
204     auto impl = receiver->SwapImplForTesting(std::move(interceptor));
205     raw_interceptor->url_store_ = std::move(impl);
206   }
207 
GetForwardingInterface()208   blink::mojom::BlobURLStore* GetForwardingInterface() override {
209     return url_store_.get();
210   }
211 
Register(mojo::PendingRemote<blink::mojom::Blob> blob,const GURL & url,RegisterCallback callback)212   void Register(mojo::PendingRemote<blink::mojom::Blob> blob,
213                 const GURL& url,
214                 RegisterCallback callback) override {
215     GetForwardingInterface()->Register(std::move(blob), target_url_,
216                                        std::move(callback));
217   }
218 
219  private:
BlobURLStoreInterceptor(GURL target_url)220   explicit BlobURLStoreInterceptor(GURL target_url) : target_url_(target_url) {}
221 
222   std::unique_ptr<blink::mojom::BlobURLStore> url_store_;
223   GURL target_url_;
224 };
225 
226 // Constructs a WebContentsDelegate that mocks a file dialog.
227 // Unlike content::FileChooserDelegate, this class doesn't make a response in
228 // RunFileChooser(), and a user needs to call Choose().
229 class DelayedFileChooserDelegate : public WebContentsDelegate {
230  public:
Choose(const base::FilePath & file)231   void Choose(const base::FilePath& file) {
232     auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
233         blink::mojom::NativeFileInfo::New(file, base::string16()));
234     std::vector<blink::mojom::FileChooserFileInfoPtr> files;
235     files.push_back(std::move(file_info));
236     listener_->FileSelected(std::move(files), base::FilePath(),
237                             blink::mojom::FileChooserParams::Mode::kOpen);
238     listener_.reset();
239   }
240 
241   // WebContentsDelegate overrides
RunFileChooser(RenderFrameHost * render_frame_host,scoped_refptr<FileSelectListener> listener,const blink::mojom::FileChooserParams & params)242   void RunFileChooser(RenderFrameHost* render_frame_host,
243                       scoped_refptr<FileSelectListener> listener,
244                       const blink::mojom::FileChooserParams& params) override {
245     listener_ = std::move(listener);
246   }
247 
EnumerateDirectory(WebContents * web_contents,scoped_refptr<FileSelectListener> listener,const base::FilePath & directory_path)248   void EnumerateDirectory(WebContents* web_contents,
249                           scoped_refptr<FileSelectListener> listener,
250                           const base::FilePath& directory_path) override {
251     listener->FileSelectionCanceled();
252   }
253 
254  private:
255   scoped_refptr<FileSelectListener> listener_;
256 };
257 
FileChooserCallback(base::RunLoop * run_loop,blink::mojom::FileChooserResultPtr result)258 void FileChooserCallback(base::RunLoop* run_loop,
259                          blink::mojom::FileChooserResultPtr result) {
260   run_loop->Quit();
261 }
262 
263 }  // namespace
264 
265 // The goal of these tests will be to "simulate" exploited renderer processes,
266 // which can send arbitrary IPC messages and confuse browser process internal
267 // state, leading to security bugs. We are trying to verify that the browser
268 // doesn't perform any dangerous operations in such cases.
269 class SecurityExploitBrowserTest : public ContentBrowserTest {
270  public:
SecurityExploitBrowserTest()271   SecurityExploitBrowserTest() {}
272 
SetUpCommandLine(base::CommandLine * command_line)273   void SetUpCommandLine(base::CommandLine* command_line) override {
274     // EmbeddedTestServer::InitializeAndListen() initializes its |base_url_|
275     // which is required below. This cannot invoke Start() however as that kicks
276     // off the "EmbeddedTestServer IO Thread" which then races with
277     // initialization in ContentBrowserTest::SetUp(), http://crbug.com/674545.
278     ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
279 
280     // Add a host resolver rule to map all outgoing requests to the test server.
281     // This allows us to use "real" hostnames in URLs, which we can use to
282     // create arbitrary SiteInstances.
283     command_line->AppendSwitchASCII(
284         network::switches::kHostResolverRules,
285         "MAP * " +
286             net::HostPortPair::FromURL(embedded_test_server()->base_url())
287                 .ToString() +
288             ",EXCLUDE localhost");
289   }
290 
SetUpOnMainThread()291   void SetUpOnMainThread() override {
292     // Complete the manual Start() after ContentBrowserTest's own
293     // initialization, ref. comment on InitializeAndListen() above.
294     embedded_test_server()->StartAcceptingConnections();
295   }
296 
297  protected:
298   // Tests that a given file path sent in a FrameHostMsg_RunFileChooser will
299   // cause renderer to be killed.
300   void TestFileChooserWithPath(const base::FilePath& path);
301 
IsolateOrigin(const std::string & hostname)302   void IsolateOrigin(const std::string& hostname) {
303     IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
304                              {hostname});
305   }
306 };
307 
TestFileChooserWithPath(const base::FilePath & path)308 void SecurityExploitBrowserTest::TestFileChooserWithPath(
309     const base::FilePath& path) {
310   GURL foo("http://foo.com/simple_page.html");
311   EXPECT_TRUE(NavigateToURL(shell(), foo));
312   EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle());
313 
314   RenderFrameHost* compromised_renderer =
315       shell()->web_contents()->GetMainFrame();
316   blink::mojom::FileChooserParamsPtr params =
317       blink::mojom::FileChooserParams::New();
318   params->default_file_name = path;
319 
320   mojo::test::BadMessageObserver bad_message_observer;
321   mojo::Remote<blink::mojom::FileChooser> chooser =
322       FileChooserImpl::CreateBoundForTesting(
323           static_cast<RenderFrameHostImpl*>(compromised_renderer));
324   chooser->OpenFileChooser(
325       std::move(params), blink::mojom::FileChooser::OpenFileChooserCallback());
326   chooser.FlushForTesting();
327   EXPECT_THAT(bad_message_observer.WaitForBadMessage(),
328               ::testing::StartsWith("FileChooser: The default file name"));
329 }
330 
331 // Ensure that we kill the renderer process if we try to give it WebUI
332 // properties and it doesn't have enabled WebUI bindings.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,SetWebUIProperty)333 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, SetWebUIProperty) {
334   GURL foo("http://foo.com/simple_page.html");
335 
336   EXPECT_TRUE(NavigateToURL(shell(), foo));
337   EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle());
338   EXPECT_EQ(0, shell()->web_contents()->GetMainFrame()->GetEnabledBindings());
339 
340   RenderFrameHost* compromised_renderer =
341       shell()->web_contents()->GetMainFrame();
342   RenderProcessHostBadIpcMessageWaiter kill_waiter(
343       compromised_renderer->GetProcess());
344   compromised_renderer->SetWebUIProperty("toolkit", "views");
345   EXPECT_EQ(bad_message::RVH_WEB_UI_BINDINGS_MISMATCH, kill_waiter.Wait());
346 }
347 
348 // This is a test for crbug.com/312016 attempting to create duplicate
349 // RenderViewHosts. SetupForDuplicateHosts sets up this test case and leaves
350 // it in a state with pending RenderViewHost. Before the commit of the new
351 // pending RenderViewHost, this test case creates a new window through the new
352 // process.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,AttemptDuplicateRenderViewHost)353 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
354                        AttemptDuplicateRenderViewHost) {
355   int32_t duplicate_routing_id = MSG_ROUTING_NONE;
356   RenderFrameHostImpl* pending_rfh = PrepareToDuplicateHosts(
357       shell(), embedded_test_server(), &duplicate_routing_id);
358   EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);
359 
360   mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
361   params->target_url = GURL("about:blank");
362   pending_rfh->CreateNewWindow(
363       std::move(params), base::BindOnce([](mojom::CreateNewWindowStatus,
364                                            mojom::CreateNewWindowReplyPtr) {}));
365   // If the above operation doesn't cause a crash, the test has succeeded!
366 }
367 
368 // This is a test for crbug.com/312016. It tries to create two RenderWidgetHosts
369 // with the same process and routing ids, which causes a collision. It is almost
370 // identical to the AttemptDuplicateRenderViewHost test case.
371 // Crashes on all platforms.  http://crbug.com/939338
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,DISABLED_AttemptDuplicateRenderWidgetHost)372 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
373                        DISABLED_AttemptDuplicateRenderWidgetHost) {
374   int duplicate_routing_id = MSG_ROUTING_NONE;
375   RenderFrameHostImpl* pending_rfh = PrepareToDuplicateHosts(
376       shell(), embedded_test_server(), &duplicate_routing_id);
377   EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);
378 
379   // Since this test executes on the UI thread and hopping threads might cause
380   // different timing in the test, let's simulate a CreateNewWidget call coming
381   // from the IO thread.  Use the existing window routing id to cause a
382   // deliberate collision.
383   pending_rfh->CreateNewPopupWidget(
384       mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>(),
385       mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost>(),
386       mojo::PendingAssociatedRemote<blink::mojom::Widget>());
387 
388   // If the above operation doesn't crash, the test has succeeded!
389 }
390 
391 // This is a test for crbug.com/444198. It tries to send a
392 // FrameHostMsg_RunFileChooser containing an invalid path. The browser should
393 // correctly terminate the renderer in these cases.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,AttemptRunFileChoosers)394 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, AttemptRunFileChoosers) {
395   TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("../../*.txt")));
396   TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("/etc/*.conf")));
397 #if defined(OS_WIN)
398   TestFileChooserWithPath(
399       base::FilePath(FILE_PATH_LITERAL("\\\\evilserver\\evilshare\\*.txt")));
400   TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("c:\\*.txt")));
401   TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("..\\..\\*.txt")));
402 #endif
403 }
404 
405 // A test for crbug.com/941008.
406 // Calling OpenFileChooser() and EnumerateChosenDirectory() for a single
407 // FileChooser instance had a problem.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,UnexpectedMethodsSequence)408 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, UnexpectedMethodsSequence) {
409   EXPECT_TRUE(NavigateToURL(shell(), GURL("http://foo.com/simple_page.html")));
410   RenderFrameHost* compromised_renderer =
411       shell()->web_contents()->GetMainFrame();
412   auto delegate = std::make_unique<DelayedFileChooserDelegate>();
413   shell()->web_contents()->SetDelegate(delegate.get());
414 
415   mojo::Remote<blink::mojom::FileChooser> chooser =
416       FileChooserImpl::CreateBoundForTesting(
417           static_cast<RenderFrameHostImpl*>(compromised_renderer));
418   base::RunLoop run_loop1;
419   base::RunLoop run_loop2;
420   chooser->OpenFileChooser(blink::mojom::FileChooserParams::New(),
421                            base::BindOnce(FileChooserCallback, &run_loop2));
422   // The following EnumerateChosenDirectory() runs the specified callback
423   // immediately regardless of the content of the first argument FilePath.
424   chooser->EnumerateChosenDirectory(
425       base::FilePath(FILE_PATH_LITERAL(":*?\"<>|")),
426       base::BindOnce(FileChooserCallback, &run_loop1));
427   run_loop1.Run();
428 
429   delegate->Choose(base::FilePath(FILE_PATH_LITERAL("foo.txt")));
430   run_loop2.Run();
431 
432   // The test passes if it doesn't crash.
433 }
434 
435 class CorsExploitBrowserTest : public ContentBrowserTest {
436  public:
437   CorsExploitBrowserTest() = default;
438 
SetUpOnMainThread()439   void SetUpOnMainThread() override {
440     host_resolver()->AddRule("*", "127.0.0.1");
441     SetupCrossSiteRedirector(embedded_test_server());
442   }
443 
444  private:
445   DISALLOW_COPY_AND_ASSIGN(CorsExploitBrowserTest);
446 };
447 
448 // Test that receiving a commit with incorrect origin properly terminates the
449 // renderer process.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,MismatchedOriginOnCommit)450 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, MismatchedOriginOnCommit) {
451   GURL start_url(embedded_test_server()->GetURL("/title1.html"));
452   EXPECT_TRUE(NavigateToURL(shell(), start_url));
453 
454   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
455                             ->GetFrameTree()
456                             ->root();
457 
458   // Navigate to a new URL, with an interceptor that replaces the origin with
459   // one that does not match params.url.
460   GURL url(embedded_test_server()->GetURL("/title2.html"));
461   PwnCommitIPC(shell()->web_contents(), url, url,
462                url::Origin::Create(GURL("http://bar.com/")));
463 
464   // Use LoadURL, as the test shouldn't wait for navigation commit.
465   NavigationController& controller = shell()->web_contents()->GetController();
466   controller.LoadURL(url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
467   EXPECT_NE(nullptr, controller.GetPendingEntry());
468   EXPECT_EQ(url, controller.GetPendingEntry()->GetURL());
469 
470   RenderProcessHostBadIpcMessageWaiter kill_waiter(
471       root->current_frame_host()->GetProcess());
472 
473   // When the IPC message is received and validation fails, the process is
474   // terminated. However, the notification for that should be processed in a
475   // separate task of the message loop, so ensure that the process is still
476   // considered alive.
477   EXPECT_TRUE(
478       root->current_frame_host()->GetProcess()->IsInitializedAndNotDead());
479 
480   EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
481 }
482 
483 namespace {
484 
485 // Interceptor that replaces |interface_params| with the specified
486 // value for the first DidCommitProvisionalLoad message it observes in the given
487 // |web_contents| while in scope.
488 class ScopedInterfaceParamsReplacer : public DidCommitNavigationInterceptor {
489  public:
ScopedInterfaceParamsReplacer(WebContents * web_contents,mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override)490   ScopedInterfaceParamsReplacer(
491       WebContents* web_contents,
492       mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override)
493       : DidCommitNavigationInterceptor(web_contents),
494         params_override_(std::move(params_override)) {}
495   ~ScopedInterfaceParamsReplacer() override = default;
496 
497  protected:
WillProcessDidCommitNavigation(RenderFrameHost * render_frame_host,NavigationRequest * navigation_request,::FrameHostMsg_DidCommitProvisionalLoad_Params * params,mojom::DidCommitProvisionalLoadInterfaceParamsPtr * interface_params)498   bool WillProcessDidCommitNavigation(
499       RenderFrameHost* render_frame_host,
500       NavigationRequest* navigation_request,
501       ::FrameHostMsg_DidCommitProvisionalLoad_Params* params,
502       mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
503       override {
504     interface_params->Swap(&params_override_);
505 
506     return true;
507   }
508 
509  private:
510   mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override_;
511 
512   DISALLOW_COPY_AND_ASSIGN(ScopedInterfaceParamsReplacer);
513 };
514 
515 }  // namespace
516 
517 // Test that, as a general rule, not receiving new
518 // DidCommitProvisionalLoadInterfaceParamsPtr for a cross-document navigation
519 // properly terminates the renderer process. There is one exception to this
520 // rule, see: RenderFrameHostImplBrowserTest.
521 // InterfaceProviderRequestIsOptionalForFirstCommit.
522 // TODO(crbug.com/718652): when all clients are converted to use
523 // BrowserInterfaceBroker, PendingReceiver<InterfaceProvider>-related code will
524 // be removed.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,MissingInterfaceProviderOnNonSameDocumentCommit)525 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
526                        MissingInterfaceProviderOnNonSameDocumentCommit) {
527   const GURL start_url(embedded_test_server()->GetURL("/title1.html"));
528   const GURL non_same_document_url(
529       embedded_test_server()->GetURL("/title2.html"));
530 
531   EXPECT_TRUE(NavigateToURL(shell(), start_url));
532 
533   RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
534       shell()->web_contents()->GetMainFrame());
535   RenderProcessHostBadIpcMessageWaiter kill_waiter(frame->GetProcess());
536 
537   NavigationHandleObserver navigation_observer(shell()->web_contents(),
538                                                non_same_document_url);
539   ScopedInterfaceParamsReplacer replacer(shell()->web_contents(), nullptr);
540   EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), non_same_document_url));
541   EXPECT_EQ(bad_message::RFH_INTERFACE_PROVIDER_MISSING, kill_waiter.Wait());
542 
543   // Verify that the death of the renderer process doesn't leave behind and
544   // leak NavigationRequests - see https://crbug.com/869193.
545   EXPECT_FALSE(frame->HasPendingCommitNavigation());
546   EXPECT_FALSE(navigation_observer.has_committed());
547   EXPECT_TRUE(navigation_observer.is_error());
548   EXPECT_TRUE(navigation_observer.last_committed_url().is_empty());
549   EXPECT_EQ(net::OK, navigation_observer.net_error_code());
550 }
551 
552 // Test that a compromised renderer cannot ask to upload an arbitrary file in
553 // OpenURL.  This is a regression test for https://crbug.com/726067.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,OpenUrl_ResourceRequestBody)554 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
555                        OpenUrl_ResourceRequestBody) {
556   GURL start_url(embedded_test_server()->GetURL("/title1.html"));
557   GURL target_url(embedded_test_server()->GetURL("/echoall"));
558   EXPECT_TRUE(NavigateToURL(shell(), start_url));
559 
560   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
561                             ->GetFrameTree()
562                             ->root();
563 
564   RenderProcessHostBadIpcMessageWaiter kill_waiter(
565       root->current_frame_host()->GetProcess());
566 
567   // Prepare a file to upload.
568   base::ScopedAllowBlockingForTesting allow_blocking;
569   base::ScopedTempDir temp_dir;
570   base::FilePath file_path;
571   std::string file_content("test-file-content");
572   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
573   ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &file_path));
574   ASSERT_TRUE(base::WriteFile(file_path, file_content));
575 
576   // Simulate an OpenURL Mojo method asking to POST a file that the renderer
577   // shouldn't have access to.
578   auto params = CreateOpenURLParams(target_url);
579   params->post_body = new network::ResourceRequestBody;
580   params->post_body->AppendFileRange(file_path, 0, file_content.size(),
581                                      base::Time());
582   params->should_replace_current_entry = true;
583 
584   static_cast<mojom::FrameHost*>(root->current_frame_host())
585       ->OpenURL(std::move(params));
586 
587   // Verify that the malicious navigation did not commit the navigation to
588   // |target_url|.
589   EXPECT_EQ(start_url, root->current_frame_host()->GetLastCommittedURL());
590 
591   // Verify that the malicious renderer got killed.
592   EXPECT_EQ(bad_message::ILLEGAL_UPLOAD_PARAMS, kill_waiter.Wait());
593 }
594 
595 // Test that forging a frame's unique name and commit won't allow changing the
596 // PageState of a cross-process FrameNavigationEntry.
597 // See https://crbug.com/766262.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,PageStateToWrongEntry)598 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PageStateToWrongEntry) {
599   IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
600 
601   // Commit a page with nested iframes and a separate cross-process iframe.
602   GURL main_url(embedded_test_server()->GetURL(
603       "a.com", "/cross_site_iframe_factory.html?a(a(a),b)"));
604   EXPECT_TRUE(NavigateToURL(shell(), main_url));
605   NavigationEntryImpl* back_entry = static_cast<NavigationEntryImpl*>(
606       shell()->web_contents()->GetController().GetLastCommittedEntry());
607   int nav_entry_id = back_entry->GetUniqueID();
608 
609   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
610                             ->GetFrameTree()
611                             ->root();
612   FrameTreeNode* child0_0 = root->child_at(0)->child_at(0);
613   std::string child0_0_unique_name = child0_0->unique_name();
614   FrameTreeNode* child1 = root->child_at(1);
615   GURL child1_url = child1->current_url();
616   int child1_pid = child1->current_frame_host()->GetProcess()->GetID();
617   blink::PageState child1_page_state =
618       back_entry->GetFrameEntry(child1)->page_state();
619 
620   // Add a history item in the nested frame.  It's important to do it there and
621   // not the main frame for the repro to work, since we don't walk the subtree
622   // when navigating back/forward between same document items.
623   TestNavigationObserver fragment_observer(shell()->web_contents());
624   EXPECT_TRUE(ExecuteScript(child0_0, "location.href = '#foo';"));
625   fragment_observer.Wait();
626 
627   // Simulate a name change IPC from the nested iframe, matching the cross-site
628   // iframe's unique name.
629   child0_0->SetFrameName("foo", child1->unique_name());
630 
631   // Simulate a back navigation from the now renamed nested iframe, which would
632   // put a PageState on the cross-site iframe's FrameNavigationEntry.  Forge a
633   // data URL within the PageState that differs from child1_url.
634   std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params> params =
635       std::make_unique<FrameHostMsg_DidCommitProvisionalLoad_Params>();
636   params->nav_entry_id = nav_entry_id;
637   params->did_create_new_entry = false;
638   params->url = GURL("about:blank");
639   params->transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
640   params->should_update_history = false;
641   params->gesture = NavigationGestureAuto;
642   params->method = "GET";
643   params->page_state =
644       blink::PageState::CreateFromURL(GURL("data:text/html,foo"));
645   params->origin = url::Origin::Create(GURL("about:blank"));
646   params->embedding_token = base::UnguessableToken::Create();
647 
648   mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
649       isolated_interface_provider;
650   static_cast<mojom::FrameHost*>(child0_0->current_frame_host())
651       ->DidCommitProvisionalLoad(
652           std::move(params),
653           mojom::DidCommitProvisionalLoadInterfaceParams::New(
654               isolated_interface_provider.InitWithNewPipeAndPassReceiver(),
655               mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>()
656                   .InitWithNewPipeAndPassReceiver()));
657 
658   // Make sure we haven't changed the FrameNavigationEntry.  An attack would
659   // modify the PageState but leave the SiteInstance as it was.
660   EXPECT_EQ(child1->current_frame_host()->GetSiteInstance(),
661             back_entry->GetFrameEntry(child1)->site_instance());
662   EXPECT_EQ(child1_page_state, back_entry->GetFrameEntry(child1)->page_state());
663 
664   // Put the frame's unique name back.
665   child0_0->SetFrameName("bar", child0_0_unique_name);
666 
667   // Go forward after the fake back navigation.
668   TestNavigationObserver forward_observer(shell()->web_contents());
669   shell()->web_contents()->GetController().GoForward();
670   forward_observer.Wait();
671 
672   // Go back to the possibly corrupted entry and ensure we didn't load the data
673   // URL in the previous process.  A test failure here would appear as a failure
674   // of the URL check and not the process ID check.
675   TestNavigationObserver back_observer(shell()->web_contents());
676   shell()->web_contents()->GetController().GoBack();
677   back_observer.Wait();
678   EXPECT_EQ(child1_pid, child1->current_frame_host()->GetProcess()->GetID());
679   ASSERT_EQ(child1_url, child1->current_url());
680 }
681 
682 class SecurityExploitBrowserTestMojoBlobURLs
683     : public SecurityExploitBrowserTest {
684  public:
685   SecurityExploitBrowserTestMojoBlobURLs() = default;
686 
TearDown()687   void TearDown() override {
688     storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr);
689   }
690 };
691 
692 // Check that when site isolation is enabled, an origin can't create a blob URL
693 // for a different origin.  Similar to the test above, but checks the
694 // mojo-based Blob URL implementation.  See https://crbug.com/886976.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestMojoBlobURLs,CreateMojoBlobURLInDifferentOrigin)695 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestMojoBlobURLs,
696                        CreateMojoBlobURLInDifferentOrigin) {
697   IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
698 
699   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
700   EXPECT_TRUE(NavigateToURL(shell(), main_url));
701   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
702 
703   // Intercept future blob URL registrations and overwrite the blob URL origin
704   // with b.com.
705   std::string target_origin = "http://b.com";
706   std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
707   auto intercept_hook =
708       base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
709                           GURL("blob:" + target_origin + "/" + blob_path));
710   storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
711 
712   // Register a blob URL from the a.com main frame, which will go through the
713   // interceptor above and be rewritten to register the blob URL with the b.com
714   // origin. This should result in a kill because a.com should not be allowed
715   // to create blob URLs outside of its own origin.
716   content::RenderProcessHostBadMojoMessageWaiter crash_observer(
717       rfh->GetProcess());
718 
719   // The renderer should always get killed, but sometimes ExecuteScript returns
720   // true anyway, so just ignore the result.
721   ignore_result(
722       content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))"));
723 
724   // If the process is killed, this test passes.
725   EXPECT_EQ(
726       "Received bad user message: "
727       "Non committable URL passed to BlobURLStore::Register",
728       crash_observer.Wait());
729 }
730 
731 // Check that with site isolation enabled, an origin can't create a filesystem
732 // URL for a different origin.  See https://crbug.com/888001.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,CreateFilesystemURLInDifferentOrigin)733 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
734                        CreateFilesystemURLInDifferentOrigin) {
735   IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
736 
737   GURL main_url(embedded_test_server()->GetURL(
738       "a.com", "/cross_site_iframe_factory.html?a(b)"));
739   EXPECT_TRUE(NavigateToURL(shell(), main_url));
740   RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
741 
742   // Block the renderer on operation that never completes, to shield it from
743   // receiving unexpected browser->renderer IPCs that might CHECK.
744   rfh->ExecuteJavaScriptWithUserGestureForTests(
745       base::ASCIIToUTF16("var r = new XMLHttpRequest();"
746                          "r.open('GET', '/slow?99999', false);"
747                          "r.send(null);"
748                          "while (1);"));
749 
750   // Set up a blob ID and populate it with attacker-controlled value. This
751   // is just using the blob APIs directly since creating arbitrary blobs is not
752   // what is prohibited; this data is not in any origin.
753   std::string payload = "<html><body>pwned.</body></html>";
754   std::string payload_type = "text/html";
755   std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
756       rfh->GetSiteInstance()->GetBrowserContext(), payload, payload_type);
757   std::string blob_id = blob->GetUUID();
758 
759   // Target a different origin.
760   std::string target_origin = "http://b.com";
761   GURL target_url =
762       GURL("filesystem:" + target_origin + "/temporary/exploit.html");
763 
764   // Note: a well-behaved renderer would always call Open first before calling
765   // Create and Write, but it's actually not necessary for the original attack
766   // to succeed, so we omit it. As a result there are some log warnings from the
767   // quota observer.
768 
769   PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url, false,
770                                      false, false);
771 
772   // Write the blob into the file. If successful, this places an
773   // attacker-controlled value in a resource on the target origin.
774   PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url, blob_id,
775                                     0);
776 
777   // Now navigate to |target_url| in a subframe. It should not succeed, and the
778   // subframe should not contain |payload|.
779   TestNavigationObserver observer(shell()->web_contents());
780   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
781                             ->GetFrameTree()
782                             ->root();
783   NavigateFrameToURL(root->child_at(0), target_url);
784   EXPECT_FALSE(observer.last_navigation_succeeded());
785   EXPECT_EQ(net::ERR_FILE_NOT_FOUND, observer.last_net_error_code());
786 
787   RenderFrameHost* attacked_rfh = root->child_at(0)->current_frame_host();
788   std::string body =
789       EvalJs(attacked_rfh, "document.body.innerText").ExtractString();
790   EXPECT_TRUE(base::StartsWith(body, "Could not load the requested resource",
791                                base::CompareCase::INSENSITIVE_ASCII))
792       << " body=" << body;
793 }
794 
795 // Verify that when a compromised renderer tries to navigate a remote frame to
796 // a disallowed URL (e.g., file URL), that navigation is blocked.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,BlockIllegalOpenURLFromRemoteFrame)797 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
798                        BlockIllegalOpenURLFromRemoteFrame) {
799   // Explicitly isolating a.com helps ensure that this test is applicable on
800   // platforms without site-per-process.
801   IsolateOrigin("a.com");
802 
803   GURL main_url(embedded_test_server()->GetURL(
804       "a.com", "/cross_site_iframe_factory.html?a(b)"));
805   EXPECT_TRUE(NavigateToURL(shell(), main_url));
806   FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
807                             ->GetFrameTree()
808                             ->root();
809   FrameTreeNode* child = root->child_at(0);
810 
811   // Simulate an IPC message where the top frame asks the remote subframe to
812   // navigate to a file: URL.
813   SiteInstance* a_com_instance = root->current_frame_host()->GetSiteInstance();
814   RenderFrameProxyHost* proxy =
815       child->render_manager()->GetRenderFrameProxyHost(a_com_instance);
816   EXPECT_TRUE(proxy);
817 
818   TestNavigationObserver observer(shell()->web_contents());
819   static_cast<mojom::FrameHost*>(proxy->frame_tree_node()->current_frame_host())
820       ->OpenURL(CreateOpenURLParams(GURL("file:///")));
821   observer.Wait();
822 
823   // Verify that the malicious navigation was blocked.  Currently, this happens
824   // by rewriting the target URL to about:blank#blocked.
825   //
826   // TODO(alexmos): Consider killing the renderer process in this case, since
827   // this security check is already enforced in the renderer process.
828   EXPECT_EQ(GURL(kBlockedURL),
829             child->current_frame_host()->GetLastCommittedURL());
830 
831   // Navigate to the starting page again to recreate the proxy, then try the
832   // same malicious navigation with a chrome:// URL.
833   EXPECT_TRUE(NavigateToURL(shell(), main_url));
834   child = root->child_at(0);
835   proxy = child->render_manager()->GetRenderFrameProxyHost(a_com_instance);
836   EXPECT_TRUE(proxy);
837 
838   TestNavigationObserver observer_2(shell()->web_contents());
839   GURL chrome_url(std::string(kChromeUIScheme) + "://" +
840                   std::string(kChromeUIGpuHost));
841   static_cast<mojom::FrameHost*>(proxy->frame_tree_node()->current_frame_host())
842       ->OpenURL(CreateOpenURLParams(chrome_url));
843   observer_2.Wait();
844   EXPECT_EQ(GURL(kBlockedURL),
845             child->current_frame_host()->GetLastCommittedURL());
846 }
847 
848 class RouteMessageEventInterceptor
849     : public blink::mojom::RemoteFrameHostInterceptorForTesting {
850  public:
RouteMessageEventInterceptor(RenderFrameProxyHost * render_frame_proxy_host,const base::string16 & evil_origin)851   explicit RouteMessageEventInterceptor(
852       RenderFrameProxyHost* render_frame_proxy_host,
853       const base::string16& evil_origin)
854       : render_frame_proxy_host_(render_frame_proxy_host),
855         evil_origin_(evil_origin) {
856     render_frame_proxy_host_->frame_host_receiver_for_testing()
857         .SwapImplForTesting(this);
858   }
859 
GetForwardingInterface()860   RemoteFrameHost* GetForwardingInterface() override {
861     return render_frame_proxy_host_;
862   }
863 
RouteMessageEvent(const base::Optional<base::UnguessableToken> & source_frame_token,const base::string16 & source_origin,const base::string16 & target_origin,blink::TransferableMessage message)864   void RouteMessageEvent(
865       const base::Optional<base::UnguessableToken>& source_frame_token,
866       const base::string16& source_origin,
867       const base::string16& target_origin,
868       blink::TransferableMessage message) override {
869     // Forward the message to the actual RFPH replacing |source_origin| with the
870     // "evil origin" as especified in SetEvilSourceOriginAndWaitForMessage().
871     GetForwardingInterface()->RouteMessageEvent(
872         std::move(source_frame_token), std::move(evil_origin_),
873         std::move(target_origin), std::move(message));
874   }
875 
876  private:
877   RenderFrameProxyHost* render_frame_proxy_host_;
878   base::string16 evil_origin_;
879 };
880 
881 // Test verifying that a compromised renderer can't lie about the source_origin
882 // passed along with the RouteMessageEvent() mojo message.  See also
883 // https://crbug.com/915721.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,PostMessageSourceOrigin)884 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PostMessageSourceOrigin) {
885   // Explicitly isolating a.com helps ensure that this test is applicable on
886   // platforms without site-per-process.
887   IsolateOrigin("b.com");
888 
889   // Navigate to a page with an OOPIF.
890   GURL main_url(embedded_test_server()->GetURL(
891       "a.com", "/cross_site_iframe_factory.html?a(b)"));
892   EXPECT_TRUE(NavigateToURL(shell(), main_url));
893 
894   // Sanity check of test setup: main frame and subframe should be isolated.
895   WebContents* web_contents = shell()->web_contents();
896   RenderFrameHost* main_frame = web_contents->GetMainFrame();
897   RenderFrameHost* subframe = web_contents->GetAllFrames()[1];
898   EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess());
899 
900   // We need to get ahold of the RenderFrameProxyHost representing the main
901   // frame for the subframe's process, to install the mojo interceptor.
902   FrameTreeNode* main_frame_node =
903       static_cast<WebContentsImpl*>(shell()->web_contents())
904           ->GetFrameTree()
905           ->root();
906   FrameTreeNode* subframe_node = main_frame_node->child_at(0);
907   SiteInstance* b_com_instance =
908       subframe_node->current_frame_host()->GetSiteInstance();
909   RenderFrameProxyHost* main_frame_proxy_host =
910       main_frame_node->render_manager()->GetRenderFrameProxyHost(
911           b_com_instance);
912 
913   // Prepare to intercept the RouteMessageEvent IPC message that will come
914   // from the subframe process.
915   url::Origin invalid_origin =
916       web_contents->GetMainFrame()->GetLastCommittedOrigin();
917   base::string16 evil_source_origin =
918       base::UTF8ToUTF16(invalid_origin.Serialize());
919   RouteMessageEventInterceptor mojo_interceptor(main_frame_proxy_host,
920                                                 evil_source_origin);
921 
922   // Post a message from the subframe to the cross-site parent and intercept the
923   // associated IPC message, changing it to simulate a compromised subframe
924   // renderer lying that the |source_origin| of the postMessage is the origin of
925   // the parent (not of the subframe).
926   RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe->GetProcess());
927   EXPECT_TRUE(ExecJs(subframe, "parent.postMessage('blah', '*')"));
928   EXPECT_EQ(bad_message::RFPH_POST_MESSAGE_INVALID_SOURCE_ORIGIN,
929             kill_waiter.Wait());
930 }
931 
932 // Intercepts calls to RenderFramHost's OpenURL mojo method, and
933 // store the passed parameter.
934 class OpenURLInterceptor
935     : public mojom::RenderFrameProxyHostInterceptorForTesting {
936  public:
OpenURLInterceptor(content::RenderFrameProxyHost * render_frame_proxy_host)937   explicit OpenURLInterceptor(
938       content::RenderFrameProxyHost* render_frame_proxy_host)
939       : render_frame_proxy_host_(render_frame_proxy_host),
940         intercepted_params_(mojom::OpenURLParams::New()) {
941     render_frame_proxy_host_->frame_proxy_host_receiver_for_testing()
942         .SwapImplForTesting(this);
943   }
944 
945   ~OpenURLInterceptor() override = default;
946 
GetForwardingInterface()947   mojom::RenderFrameProxyHost* GetForwardingInterface() override {
948     return render_frame_proxy_host_;
949   }
950 
OpenURL(mojom::OpenURLParamsPtr params)951   void OpenURL(mojom::OpenURLParamsPtr params) override {
952     intercepted_params_ = std::move(params);
953   }
954 
GetInterceptedParams()955   mojom::OpenURLParamsPtr GetInterceptedParams() {
956     return std::move(intercepted_params_);
957   }
958 
959  private:
960   content::RenderFrameProxyHost* render_frame_proxy_host_;
961   mojom::OpenURLParamsPtr intercepted_params_;
962 
963   DISALLOW_COPY_AND_ASSIGN(OpenURLInterceptor);
964 };
965 
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,InvalidRemoteNavigationInitiator)966 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
967                        InvalidRemoteNavigationInitiator) {
968   // Explicitly isolating a.com helps ensure that this test is applicable on
969   // platforms without site-per-process.
970   IsolateOrigin("a.com");
971 
972   // Navigate to a test page where the subframe is cross-site (and because of
973   // IsolateOrigin call above in a separate process) from the main frame.
974   GURL main_url(embedded_test_server()->GetURL(
975       "a.com", "/cross_site_iframe_factory.html?a(b)"));
976   EXPECT_TRUE(NavigateToURL(shell(), main_url));
977   RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
978       shell()->web_contents()->GetMainFrame());
979   RenderProcessHost* main_process = main_frame->GetProcess();
980   EXPECT_EQ(2u, shell()->web_contents()->GetAllFrames().size());
981   RenderFrameHost* subframe = shell()->web_contents()->GetAllFrames()[1];
982   RenderProcessHost* subframe_process = subframe->GetProcess();
983   EXPECT_NE(main_process->GetID(), subframe_process->GetID());
984 
985   // Prepare to intercept OpenURL Mojo message that will come from
986   // the main frame.
987   FrameTreeNode* main_frame_node =
988       static_cast<WebContentsImpl*>(shell()->web_contents())
989           ->GetFrameTree()
990           ->root();
991   FrameTreeNode* child_node = main_frame_node->child_at(0);
992   SiteInstance* a_com_instance =
993       main_frame_node->current_frame_host()->GetSiteInstance();
994   RenderFrameProxyHost* proxy =
995       child_node->render_manager()->GetRenderFrameProxyHost(a_com_instance);
996 
997   OpenURLInterceptor interceptor(proxy);
998 
999   // Have the main frame request navigation in the "remote" subframe.  This will
1000   // result in OpenURL Mojo message being sent to the RenderFrameProxyHost.
1001   EXPECT_TRUE(ExecJs(shell()->web_contents()->GetMainFrame(),
1002                      "window.frames[0].location = '/title1.html';"));
1003 
1004   // Change the intercepted message to simulate a compromised subframe renderer
1005   // lying that the |initiator_origin| is the origin of the |subframe|.
1006   auto evil_params = interceptor.GetInterceptedParams();
1007   evil_params->initiator_origin = subframe->GetLastCommittedOrigin();
1008 
1009   // Inject the invalid IPC and verify that the renderer gets terminated.
1010   RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
1011   static_cast<mojom::FrameHost*>(main_frame)->OpenURL(std::move(evil_params));
1012   EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
1013 }
1014 
1015 class BeginNavigationInitiatorReplacer : public FrameHostInterceptor {
1016  public:
BeginNavigationInitiatorReplacer(WebContents * web_contents,base::Optional<url::Origin> initiator_to_inject)1017   BeginNavigationInitiatorReplacer(
1018       WebContents* web_contents,
1019       base::Optional<url::Origin> initiator_to_inject)
1020       : FrameHostInterceptor(web_contents),
1021         initiator_to_inject_(initiator_to_inject) {}
1022 
WillDispatchBeginNavigation(RenderFrameHost * render_frame_host,mojom::CommonNavigationParamsPtr * common_params,mojom::BeginNavigationParamsPtr * begin_params,mojo::PendingRemote<blink::mojom::BlobURLToken> * blob_url_token,mojo::PendingAssociatedRemote<mojom::NavigationClient> * navigation_client,mojo::PendingRemote<blink::mojom::NavigationInitiator> * navigation_initiator)1023   bool WillDispatchBeginNavigation(
1024       RenderFrameHost* render_frame_host,
1025       mojom::CommonNavigationParamsPtr* common_params,
1026       mojom::BeginNavigationParamsPtr* begin_params,
1027       mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
1028       mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client,
1029       mojo::PendingRemote<blink::mojom::NavigationInitiator>*
1030           navigation_initiator) override {
1031     if (is_activated_) {
1032       (*common_params)->initiator_origin = initiator_to_inject_;
1033       is_activated_ = false;
1034     }
1035 
1036     return true;
1037   }
1038 
Activate()1039   void Activate() { is_activated_ = true; }
1040 
1041  private:
1042   base::Optional<url::Origin> initiator_to_inject_;
1043   bool is_activated_ = false;
1044 
1045   DISALLOW_COPY_AND_ASSIGN(BeginNavigationInitiatorReplacer);
1046 };
1047 
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,InvalidBeginNavigationInitiator)1048 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1049                        InvalidBeginNavigationInitiator) {
1050   WebContentsImpl* web_contents =
1051       static_cast<WebContentsImpl*>(shell()->web_contents());
1052 
1053   // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
1054   // the test creates the RenderFrameHostImpl that is the target of the IPC.
1055   BeginNavigationInitiatorReplacer injector(
1056       web_contents, url::Origin::Create(GURL("http://b.com")));
1057 
1058   // Explicitly isolating a.com helps ensure that this test is applicable on
1059   // platforms without site-per-process.
1060   IsolateOrigin("a.com");
1061 
1062   // Navigate to a test page that will be locked to a.com.
1063   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1064   EXPECT_TRUE(NavigateToURL(web_contents, main_url));
1065 
1066   // Start monitoring for renderer kills.
1067   RenderProcessHost* main_process = web_contents->GetMainFrame()->GetProcess();
1068   RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
1069 
1070   // Have the main frame navigate and lie that the initiator origin is b.com.
1071   injector.Activate();
1072   // Don't expect a response for the script, as the process may be killed
1073   // before the script sends its completion message.
1074   ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
1075 
1076   // Verify that the renderer was terminated.
1077   EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
1078 }
1079 
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,MissingBeginNavigationInitiator)1080 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1081                        MissingBeginNavigationInitiator) {
1082   // Prepare to intercept BeginNavigation mojo IPC.  This has to be done before
1083   // the test creates the RenderFrameHostImpl that is the target of the IPC.
1084   WebContents* web_contents = shell()->web_contents();
1085   BeginNavigationInitiatorReplacer injector(web_contents, base::nullopt);
1086 
1087   // Navigate to a test page.
1088   GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1089   EXPECT_TRUE(NavigateToURL(web_contents, main_url));
1090 
1091   // Start monitoring for renderer kills.
1092   RenderProcessHost* main_process = web_contents->GetMainFrame()->GetProcess();
1093   RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
1094 
1095   // Have the main frame submit a BeginNavigation IPC with a missing initiator.
1096   injector.Activate();
1097   // Don't expect a response for the script, as the process may be killed
1098   // before the script sends its completion message.
1099   ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
1100 
1101   // Verify that the renderer was terminated.
1102   EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_MISSING_INITIATOR_ORIGIN,
1103             kill_waiter.Wait());
1104 }
1105 
1106 namespace {
1107 
1108 // An interceptor class that allows replacing the URL of the commit IPC from
1109 // the renderer process to the browser process.
1110 class DidCommitUrlReplacer : public DidCommitNavigationInterceptor {
1111  public:
DidCommitUrlReplacer(WebContents * web_contents,const GURL & replacement_url)1112   DidCommitUrlReplacer(WebContents* web_contents, const GURL& replacement_url)
1113       : DidCommitNavigationInterceptor(web_contents),
1114         replacement_url_(replacement_url) {}
1115   ~DidCommitUrlReplacer() override = default;
1116 
1117  protected:
WillProcessDidCommitNavigation(RenderFrameHost * render_frame_host,NavigationRequest * navigation_request,::FrameHostMsg_DidCommitProvisionalLoad_Params * params,mojom::DidCommitProvisionalLoadInterfaceParamsPtr * interface_params)1118   bool WillProcessDidCommitNavigation(
1119       RenderFrameHost* render_frame_host,
1120       NavigationRequest* navigation_request,
1121       ::FrameHostMsg_DidCommitProvisionalLoad_Params* params,
1122       mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
1123       override {
1124     params->url = replacement_url_;
1125     return true;
1126   }
1127 
1128  private:
1129   GURL replacement_url_;
1130 
1131   DISALLOW_COPY_AND_ASSIGN(DidCommitUrlReplacer);
1132 };
1133 
1134 }  // namespace
1135 
1136 // Test which verifies that when an exploited renderer process sends a commit
1137 // message with URL that the process is not allowed to commit.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,DidCommitInvalidURL)1138 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, DidCommitInvalidURL) {
1139   // Explicitly isolating foo.com helps ensure that this test is applicable on
1140   // platforms without site-per-process.
1141   IsolateOrigin("foo.com");
1142 
1143   RenderFrameDeletedObserver initial_frame_deleted_observer(
1144       shell()->web_contents()->GetMainFrame());
1145 
1146   // Test assumes the initial RenderFrameHost to be deleted. Disable
1147   // back-forward cache to ensure that it doesn't get preserved in the cache.
1148   DisableBackForwardCacheForTesting(shell()->web_contents(),
1149                                     BackForwardCache::TEST_ASSUMES_NO_CACHING);
1150 
1151   // Navigate to foo.com initially.
1152   GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
1153   EXPECT_TRUE(NavigateToURL(shell(), foo_url));
1154 
1155   // Wait for the RenderFrameHost which was current before the navigation to
1156   // foo.com to be deleted. This is necessary, since on a slow system the
1157   // UnloadACK event can arrive after the DidCommitUrlReplacer instance below
1158   // is created. The replacer code has checks to ensure that all frames being
1159   // deleted it has seen being created, which with delayed UnloadACK is
1160   // violated.
1161   initial_frame_deleted_observer.WaitUntilDeleted();
1162 
1163   // Create the interceptor object which will replace the URL of the subsequent
1164   // navigation with bar.com based URL.
1165   GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
1166   DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url);
1167 
1168   // Navigate to another URL within foo.com, which would usually be committed
1169   // successfully, but when the URL is modified it should result in the
1170   // termination of the renderer process.
1171   RenderProcessHostBadIpcMessageWaiter kill_waiter(
1172       shell()->web_contents()->GetMainFrame()->GetProcess());
1173   EXPECT_FALSE(NavigateToURL(
1174       shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
1175   EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
1176 }
1177 
1178 // Test which verifies that when an exploited renderer process sends a commit
1179 // message with URL that the process is not allowed to commit.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,DidCommitInvalidURLWithOpaqueOrigin)1180 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1181                        DidCommitInvalidURLWithOpaqueOrigin) {
1182   // Explicitly isolating foo.com helps ensure that this test is applicable on
1183   // platforms without site-per-process.
1184   IsolateOrigin("foo.com");
1185 
1186   RenderFrameDeletedObserver initial_frame_deleted_observer(
1187       shell()->web_contents()->GetMainFrame());
1188 
1189   // Test assumes the initial RenderFrameHost to be deleted. Disable
1190   // back-forward cache to ensure that it doesn't get preserved in the cache.
1191   DisableBackForwardCacheForTesting(shell()->web_contents(),
1192                                     BackForwardCache::TEST_ASSUMES_NO_CACHING);
1193 
1194   // Navigate to foo.com initially.
1195   GURL foo_url(embedded_test_server()->GetURL("foo.com",
1196                                               "/page_with_blank_iframe.html"));
1197   EXPECT_TRUE(NavigateToURL(shell(), foo_url));
1198 
1199   // Wait for the RenderFrameHost which was current before the navigation to
1200   // foo.com to be deleted. This is necessary, since on a slow system the
1201   // UnloadACK event can arrive after the DidCommitUrlReplacer instance below
1202   // is created. The replacer code has checks to ensure that all frames being
1203   // deleted it has seen being created, which with delayed UnloadACK is
1204   // violated.
1205   initial_frame_deleted_observer.WaitUntilDeleted();
1206 
1207   // Create the interceptor object which will replace the URL of the subsequent
1208   // navigation with bar.com based URL.
1209   GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
1210   DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url);
1211 
1212   // Navigate the subframe to a data URL, which would usually be committed
1213   // successfully in the same process as foo.com, but when the URL is modified
1214   // it should result in the termination of the renderer process.
1215   RenderProcessHostBadIpcMessageWaiter kill_waiter(
1216       shell()->web_contents()->GetMainFrame()->GetProcess());
1217   GURL data_url(R"(data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E)");
1218   EXPECT_TRUE(
1219       NavigateIframeToURL(shell()->web_contents(), "test_iframe", data_url));
1220   EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
1221 }
1222 
1223 // Test which verifies that a WebUI process cannot send a commit message with
1224 // URL for a web document.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,WebUIProcessDidCommitWebURL)1225 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1226                        WebUIProcessDidCommitWebURL) {
1227   // Navigate to a WebUI document.
1228   GURL webui_url(GetWebUIURL(kChromeUIGpuHost));
1229   EXPECT_TRUE(NavigateToURL(shell(), webui_url));
1230 
1231   // Create the interceptor object which will replace the URL of the subsequent
1232   // navigation with |web_url|.
1233   GURL web_url(embedded_test_server()->GetURL("foo.com", "/title3.html"));
1234   DidCommitUrlReplacer url_replacer(shell()->web_contents(), web_url);
1235 
1236   // Navigate to another URL within the WebUI, which would usually be committed
1237   // successfully, but when the URL is modified it should result in the
1238   // termination of the renderer process.
1239   RenderProcessHostBadIpcMessageWaiter kill_waiter(
1240       shell()->web_contents()->GetMainFrame()->GetProcess());
1241   GURL second_webui_url(webui_url.Resolve("/foo"));
1242   EXPECT_FALSE(NavigateToURL(shell(), second_webui_url));
1243   EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
1244 }
1245 
1246 // Test that verifies that if a RenderFrameHost is incorrectly given WebUI
1247 // bindings, committing a non-WebUI URL in it is detected and the process is
1248 // correctly terminated.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,DidCommitNonWebUIURLInProcessWithBindings)1249 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1250                        DidCommitNonWebUIURLInProcessWithBindings) {
1251   // Navigate to a web URL.
1252   GURL initial_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
1253   EXPECT_TRUE(NavigateToURL(shell(), initial_url));
1254 
1255   // Start a second navigation.
1256   GURL web_url(embedded_test_server()->GetURL("foo.com", "/title2.html"));
1257   TestNavigationManager navigation(shell()->web_contents(), web_url);
1258   RenderProcessHostBadIpcMessageWaiter kill_waiter(
1259       shell()->web_contents()->GetMainFrame()->GetProcess());
1260 
1261   shell()->LoadURL(web_url);
1262   EXPECT_TRUE(navigation.WaitForResponse());
1263 
1264   // Grant WebUI bindings to the navigated frame to simulate a bug in the code
1265   // that incorrectly does it for a navigation that does not require it.
1266   navigation.GetNavigationHandle()->GetRenderFrameHost()->AllowBindings(
1267       BINDINGS_POLICY_WEB_UI);
1268 
1269   // Resume the navigation and upon receiving the commit message the renderer
1270   // process will be terminated.
1271   navigation.ResumeNavigation();
1272   EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
1273 }
1274 
1275 class BeginNavigationTransitionReplacer : public FrameHostInterceptor {
1276  public:
BeginNavigationTransitionReplacer(WebContents * web_contents,ui::PageTransition transition_to_inject)1277   BeginNavigationTransitionReplacer(WebContents* web_contents,
1278                                     ui::PageTransition transition_to_inject)
1279       : FrameHostInterceptor(web_contents),
1280         transition_to_inject_(transition_to_inject) {}
1281 
WillDispatchBeginNavigation(RenderFrameHost * render_frame_host,mojom::CommonNavigationParamsPtr * common_params,mojom::BeginNavigationParamsPtr * begin_params,mojo::PendingRemote<blink::mojom::BlobURLToken> * blob_url_token,mojo::PendingAssociatedRemote<mojom::NavigationClient> * navigation_client,mojo::PendingRemote<blink::mojom::NavigationInitiator> * navigation_initiator)1282   bool WillDispatchBeginNavigation(
1283       RenderFrameHost* render_frame_host,
1284       mojom::CommonNavigationParamsPtr* common_params,
1285       mojom::BeginNavigationParamsPtr* begin_params,
1286       mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
1287       mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client,
1288       mojo::PendingRemote<blink::mojom::NavigationInitiator>*
1289           navigation_initiator) override {
1290     if (is_activated_) {
1291       (*common_params)->transition = transition_to_inject_;
1292       is_activated_ = false;
1293     }
1294 
1295     return true;
1296   }
1297 
Activate()1298   void Activate() { is_activated_ = true; }
1299 
1300  private:
1301   ui::PageTransition transition_to_inject_;
1302   bool is_activated_ = false;
1303 
1304   DISALLOW_COPY_AND_ASSIGN(BeginNavigationTransitionReplacer);
1305 };
1306 
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,NonWebbyTransition)1307 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, NonWebbyTransition) {
1308   const ui::PageTransition test_cases[] = {
1309       ui::PAGE_TRANSITION_TYPED,
1310       ui::PAGE_TRANSITION_AUTO_BOOKMARK,
1311       ui::PAGE_TRANSITION_GENERATED,
1312       ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1313       ui::PAGE_TRANSITION_RELOAD,
1314       ui::PAGE_TRANSITION_KEYWORD,
1315       ui::PAGE_TRANSITION_KEYWORD_GENERATED};
1316 
1317   for (ui::PageTransition transition : test_cases) {
1318     // Prepare to intercept BeginNavigation mojo IPC.  This has to be done
1319     // before the test creates the RenderFrameHostImpl that is the target of the
1320     // IPC.
1321     WebContents* web_contents = shell()->web_contents();
1322     BeginNavigationTransitionReplacer injector(web_contents, transition);
1323 
1324     // Navigate to a test page.
1325     GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1326     EXPECT_TRUE(NavigateToURL(web_contents, main_url));
1327 
1328     // Start monitoring for renderer kills.
1329     RenderProcessHost* main_process =
1330         web_contents->GetMainFrame()->GetProcess();
1331     RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
1332 
1333     // Have the main frame submit a BeginNavigation IPC with a missing
1334     // initiator.
1335     injector.Activate();
1336     // Don't expect a response for the script, as the process may be killed
1337     // before the script sends its completion message.
1338     ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
1339 
1340     // Verify that the renderer was terminated.
1341     EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION,
1342               kill_waiter.Wait());
1343   }
1344 }
1345 
1346 class SecurityExploitViaDisabledWebSecurityTest
1347     : public SecurityExploitBrowserTest {
1348  public:
SecurityExploitViaDisabledWebSecurityTest()1349   SecurityExploitViaDisabledWebSecurityTest() {
1350     // To get around BlockedSchemeNavigationThrottle. Other attempts at getting
1351     // around it don't work, i.e.:
1352     // -if the request is made in a child frame then the frame is torn down
1353     // immediately on process killing so the navigation doesn't complete
1354     // -if it's classified as same document, then a DCHECK in
1355     // NavigationRequest::CreateRendererInitiated fires
1356     feature_list_.InitAndEnableFeature(
1357         features::kAllowContentInitiatedDataUrlNavigations);
1358   }
1359 
1360  protected:
SetUpCommandLine(base::CommandLine * command_line)1361   void SetUpCommandLine(base::CommandLine* command_line) override {
1362     // Simulate a compromised renderer, otherwise the cross-origin request to
1363     // file: is blocked.
1364     command_line->AppendSwitch(switches::kDisableWebSecurity);
1365     SecurityExploitBrowserTest::SetUpCommandLine(command_line);
1366   }
1367 
1368  private:
1369   base::test::ScopedFeatureList feature_list_;
1370 };
1371 
1372 // Test to verify that an exploited renderer process trying to specify a
1373 // non-empty URL for base_url_for_data_url on navigation is correctly
1374 // terminated.
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,ValidateBaseUrlForDataUrl)1375 IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
1376                        ValidateBaseUrlForDataUrl) {
1377   GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1378   EXPECT_TRUE(NavigateToURL(shell(), start_url));
1379 
1380   RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
1381       shell()->web_contents()->GetMainFrame());
1382 
1383   GURL data_url("data:text/html,foo");
1384   base::FilePath file_path = GetTestFilePath("", "simple_page.html");
1385   GURL file_url = net::FilePathToFileURL(file_path);
1386 
1387   // Setup a BeginNavigate IPC with non-empty base_url_for_data_url.
1388   mojom::CommonNavigationParamsPtr common_params =
1389       mojom::CommonNavigationParams::New(
1390           data_url, url::Origin::Create(data_url),
1391           blink::mojom::Referrer::New(), ui::PAGE_TRANSITION_LINK,
1392           mojom::NavigationType::DIFFERENT_DOCUMENT, NavigationDownloadPolicy(),
1393           false /* should_replace_current_entry */,
1394           file_url, /* base_url_for_data_url */
1395           GURL() /* history_url_for_data_url */,
1396           blink::PreviewsTypes::PREVIEWS_UNSPECIFIED,
1397           base::TimeTicks::Now() /* navigation_start */, "GET",
1398           nullptr /* post_data */, network::mojom::SourceLocation::New(),
1399           false /* started_from_context_menu */, false /* has_user_gesture */,
1400           false /* text_fragment_token */, CreateInitiatorCSPInfo(),
1401           std::vector<int>() /* initiator_origin_trial_features */,
1402           std::string() /* href_translate */,
1403           false /* is_history_navigation_in_new_child_frame */,
1404           base::TimeTicks() /* input_start */);
1405   mojom::BeginNavigationParamsPtr begin_params =
1406       mojom::BeginNavigationParams::New(
1407           MSG_ROUTING_NONE /* initiator_routing_id */,
1408           std::string() /* headers */, net::LOAD_NORMAL,
1409           false /* skip_service_worker */,
1410           blink::mojom::RequestContextType::LOCATION,
1411           network::mojom::RequestDestination::kDocument,
1412           blink::WebMixedContentContextType::kBlockable,
1413           false /* is_form_submission */,
1414           false /* was_initiated_by_link_click */,
1415           GURL() /* searchable_form_url */,
1416           std::string() /* searchable_form_encoding */,
1417           GURL() /* client_side_redirect_url */,
1418           base::nullopt /* devtools_initiator_info */,
1419           false /* force_ignore_site_for_cookies */,
1420           nullptr /* trust_token_params */, base::nullopt /* impression */,
1421           base::TimeTicks() /* renderer_before_unload_start */,
1422           base::TimeTicks() /* renderer_before_unload_end */);
1423 
1424   // Receiving the invalid IPC message should lead to renderer process
1425   // termination.
1426   RenderProcessHostBadIpcMessageWaiter process_kill_waiter(rfh->GetProcess());
1427 
1428   mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client;
1429   auto navigation_client_receiver =
1430       navigation_client.InitWithNewEndpointAndPassReceiver();
1431   rfh->frame_host_receiver_for_testing().impl()->BeginNavigation(
1432       std::move(common_params), std::move(begin_params), mojo::NullRemote(),
1433       std::move(navigation_client), mojo::NullRemote());
1434   EXPECT_EQ(bad_message::RFH_BASE_URL_FOR_DATA_URL_SPECIFIED,
1435             process_kill_waiter.Wait());
1436 
1437   EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
1438       rfh->GetProcess()->GetID(), file_path));
1439 
1440   // Reload the page to create another renderer process.
1441   TestNavigationObserver tab_observer(shell()->web_contents(), 1);
1442   shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
1443   tab_observer.Wait();
1444 
1445   // Make an XHR request to check if the page has access.
1446   std::string script = base::StringPrintf(
1447       "var xhr = new XMLHttpRequest()\n"
1448       "xhr.open('GET', '%s', false);\n"
1449       "try { xhr.send(); } catch (e) {}\n"
1450       "window.domAutomationController.send(xhr.responseText);",
1451       file_url.spec().c_str());
1452   std::string result;
1453   EXPECT_TRUE(
1454       ExecuteScriptAndExtractString(shell()->web_contents(), script, &result));
1455   EXPECT_TRUE(result.empty());
1456 }
1457 
1458 // Tests what happens when a web renderer asks to begin navigating to a file
1459 // url.
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,WebToFileNavigation)1460 IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
1461                        WebToFileNavigation) {
1462   // Navigate to a web page.
1463   GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1464   EXPECT_TRUE(NavigateToURL(shell(), start_url));
1465 
1466   // Have the webpage attempt to open a window with a file URL.
1467   //
1468   // Note that such attempt would normally be blocked in the renderer ("Not
1469   // allowed to load local resource: file:///..."), but the test here simulates
1470   // a compromised renderer by using --disable-web-security cmdline flag.
1471   GURL file_url = GetTestUrl("", "simple_page.html");
1472   WebContentsAddedObserver new_window_observer;
1473   TestNavigationObserver nav_observer(nullptr);
1474   nav_observer.StartWatchingNewWebContents();
1475   ASSERT_TRUE(ExecJs(shell()->web_contents(),
1476                      JsReplace("window.open($1, '_blank')", file_url)));
1477   WebContents* new_window = new_window_observer.GetWebContents();
1478   nav_observer.WaitForNavigationFinished();
1479 
1480   // Verify that the navigation got blocked.
1481   EXPECT_TRUE(nav_observer.last_navigation_succeeded());
1482   EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
1483   EXPECT_EQ(GURL(kBlockedURL),
1484             new_window->GetMainFrame()->GetLastCommittedURL());
1485   EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin(),
1486             new_window->GetMainFrame()->GetLastCommittedOrigin());
1487   EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetProcess(),
1488             new_window->GetMainFrame()->GetProcess());
1489 
1490   // Even though the navigation is blocked, we expect the opener relationship to
1491   // be established between the 2 windows.
1492   EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
1493 }
1494 
1495 // Tests what happens when a web renderer asks to begin navigating to a
1496 // view-source url.
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,WebToViewSourceNavigation)1497 IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
1498                        WebToViewSourceNavigation) {
1499   // Navigate to a web page.
1500   GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1501   EXPECT_TRUE(NavigateToURL(shell(), start_url));
1502 
1503   // Have the webpage attempt to open a window with a view-source URL.
1504   //
1505   // Note that such attempt would normally be blocked in the renderer ("Not
1506   // allowed to load local resource: view-source:///..."), but the test here
1507   // simulates a compromised renderer by using --disable-web-security flag.
1508   base::FilePath file_path = GetTestFilePath("", "simple_page.html");
1509   GURL view_source_url =
1510       GURL(std::string(kViewSourceScheme) + ":" + start_url.spec());
1511   WebContentsAddedObserver new_window_observer;
1512   TestNavigationObserver nav_observer(nullptr);
1513   nav_observer.StartWatchingNewWebContents();
1514   ASSERT_TRUE(ExecJs(shell()->web_contents(),
1515                      JsReplace("window.open($1, '_blank')", view_source_url)));
1516   WebContents* new_window = new_window_observer.GetWebContents();
1517   nav_observer.WaitForNavigationFinished();
1518 
1519   // Verify that the navigation got blocked.
1520   EXPECT_TRUE(nav_observer.last_navigation_succeeded());
1521   EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
1522   EXPECT_EQ(GURL(kBlockedURL),
1523             new_window->GetMainFrame()->GetLastCommittedURL());
1524   EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin(),
1525             new_window->GetMainFrame()->GetLastCommittedOrigin());
1526   EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetProcess(),
1527             new_window->GetMainFrame()->GetProcess());
1528 
1529   // Even though the navigation is blocked, we expect the opener relationship to
1530   // be established between the 2 windows.
1531   EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
1532 }
1533 
1534 class BeginNavigationTrustTokenParamsReplacer : public FrameHostInterceptor {
1535  public:
BeginNavigationTrustTokenParamsReplacer(WebContents * web_contents,network::mojom::TrustTokenParamsPtr params_to_inject)1536   BeginNavigationTrustTokenParamsReplacer(
1537       WebContents* web_contents,
1538       network::mojom::TrustTokenParamsPtr params_to_inject)
1539       : FrameHostInterceptor(web_contents),
1540         params_to_inject_(std::move(params_to_inject)) {}
1541 
1542   BeginNavigationTrustTokenParamsReplacer(
1543       const BeginNavigationTrustTokenParamsReplacer&) = delete;
1544   BeginNavigationTrustTokenParamsReplacer& operator=(
1545       const BeginNavigationTrustTokenParamsReplacer&) = delete;
1546 
WillDispatchBeginNavigation(RenderFrameHost * render_frame_host,mojom::CommonNavigationParamsPtr * common_params,mojom::BeginNavigationParamsPtr * begin_params,mojo::PendingRemote<blink::mojom::BlobURLToken> * blob_url_token,mojo::PendingAssociatedRemote<mojom::NavigationClient> * navigation_client,mojo::PendingRemote<blink::mojom::NavigationInitiator> * navigation_initiator)1547   bool WillDispatchBeginNavigation(
1548       RenderFrameHost* render_frame_host,
1549       mojom::CommonNavigationParamsPtr* common_params,
1550       mojom::BeginNavigationParamsPtr* begin_params,
1551       mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
1552       mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client,
1553       mojo::PendingRemote<blink::mojom::NavigationInitiator>*
1554           navigation_initiator) override {
1555     if (is_activated_) {
1556       (*begin_params)->trust_token_params = params_to_inject_.Clone();
1557       is_activated_ = false;
1558     }
1559 
1560     return true;
1561   }
1562 
Activate()1563   void Activate() { is_activated_ = true; }
1564 
1565  private:
1566   network::mojom::TrustTokenParamsPtr params_to_inject_;
1567   bool is_activated_ = false;
1568 };
1569 
1570 class SecurityExploitBrowserTestWithTrustTokensEnabled
1571     : public SecurityExploitBrowserTest {
1572  public:
SecurityExploitBrowserTestWithTrustTokensEnabled()1573   SecurityExploitBrowserTestWithTrustTokensEnabled() {
1574     feature_list_.InitAndEnableFeature(network::features::kTrustTokens);
1575   }
1576 
1577  private:
1578   base::test::ScopedFeatureList feature_list_;
1579 };
1580 
1581 // Test that the browser correctly reports a bad message when a child frame
1582 // attempts to navigate with a Trust Tokens redemption operation associated with
1583 // the navigation, but its parent lacks the trust-token-redemption Feature
1584 // Policy feature.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,BrowserForbidsTrustTokenRedemptionWithoutFeaturePolicy)1585 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,
1586                        BrowserForbidsTrustTokenRedemptionWithoutFeaturePolicy) {
1587   WebContents* web_contents = shell()->web_contents();
1588 
1589   // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
1590   // the test creates the RenderFrameHostImpl that is the target of the IPC.
1591   auto params = network::mojom::TrustTokenParams::New();
1592   params->type = network::mojom::TrustTokenOperationType::kRedemption;
1593   BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
1594                                                    std::move(params));
1595 
1596   GURL start_url(embedded_test_server()->GetURL(
1597       "/page-with-trust-token-feature-policy-disabled.html"));
1598   EXPECT_TRUE(NavigateToURL(shell(), start_url));
1599 
1600   RenderFrameHost* parent = web_contents->GetMainFrame();
1601   ASSERT_FALSE(parent->IsFeatureEnabled(
1602       blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption));
1603 
1604   RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
1605                                ->GetFrameTree()
1606                                ->root()
1607                                ->child_at(0)
1608                                ->current_frame_host();
1609   RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
1610 
1611   replacer.Activate();
1612 
1613   // Note: this can't use NavigateFrameToURL, because that method doesn't
1614   // route through RFHI::BeginNavigation. It also can't use NavigateIframeToURL,
1615   // because that navigation will hang.
1616   //
1617   // It also can't EXPECT_TRUE or EXPECT_FALSE: sometimes the ExecJs call will
1618   // finish before the renderer gets killed, and sometimes it won't.
1619   ignore_result(
1620       ExecJs(child, JsReplace("location.href=$1;", GURL("/title2.html"))));
1621 
1622   EXPECT_THAT(kill_waiter.Wait(),
1623               Optional(HasSubstr("Feature Policy feature is absent")));
1624 }
1625 
1626 // Test that the browser correctly reports a bad message when a child frame
1627 // attempts to navigate with a Trust Tokens signing operation associated with
1628 // the navigation, but its parent lacks the trust-token-redemption (sic) Feature
1629 // Policy feature.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,BrowserForbidsTrustTokenSigningWithoutFeaturePolicy)1630 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,
1631                        BrowserForbidsTrustTokenSigningWithoutFeaturePolicy) {
1632   WebContents* web_contents = shell()->web_contents();
1633 
1634   // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
1635   // the test creates the RenderFrameHostImpl that is the target of the IPC.
1636   auto params = network::mojom::TrustTokenParams::New();
1637   params->type = network::mojom::TrustTokenOperationType::kSigning;
1638   BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
1639                                                    std::move(params));
1640 
1641   GURL start_url(embedded_test_server()->GetURL(
1642       "/page-with-trust-token-feature-policy-disabled.html"));
1643   EXPECT_TRUE(NavigateToURL(shell(), start_url));
1644 
1645   RenderFrameHost* parent = web_contents->GetMainFrame();
1646   ASSERT_FALSE(parent->IsFeatureEnabled(
1647       blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption));
1648 
1649   RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
1650                                ->GetFrameTree()
1651                                ->root()
1652                                ->child_at(0)
1653                                ->current_frame_host();
1654   RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
1655 
1656   replacer.Activate();
1657 
1658   // Note: this can't use NavigateFrameToURL, because that method doesn't
1659   // route through RFHI::BeginNavigation. It also can't use NavigateIframeToURL,
1660   // because that navigation will hang.
1661   //
1662   // It also can't EXPECT_TRUE or EXPECT_FALSE: sometimes the ExecJs call will
1663   // finish before the renderer gets killed, and sometimes it won't.
1664   ignore_result(
1665       ExecJs(child, JsReplace("location.href=$1;", GURL("/title2.html"))));
1666 
1667   EXPECT_THAT(kill_waiter.Wait(),
1668               Optional(HasSubstr("Feature Policy feature is absent")));
1669 }
1670 
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,BrowserForbidsTrustTokenParamsOnMainFrameNav)1671 IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,
1672                        BrowserForbidsTrustTokenParamsOnMainFrameNav) {
1673   WebContents* web_contents = shell()->web_contents();
1674 
1675   // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
1676   // the test creates the RenderFrameHostImpl that is the target of the IPC.
1677   BeginNavigationTrustTokenParamsReplacer replacer(
1678       web_contents, network::mojom::TrustTokenParams::New());
1679 
1680   GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1681   EXPECT_TRUE(NavigateToURL(shell(), start_url));
1682 
1683   RenderFrameHost* compromised_renderer = web_contents->GetMainFrame();
1684   RenderProcessHostBadMojoMessageWaiter kill_waiter(
1685       compromised_renderer->GetProcess());
1686 
1687   replacer.Activate();
1688 
1689   // Can't use NavigateToURL here because it would hang. Additionally, we can't
1690   // EXPECT_TRUE or EXPECT_FALSE: sometimes the ExecJs call will finish
1691   // before the renderer gets killed, and sometimes it won't.
1692   ignore_result(ExecJs(compromised_renderer,
1693                        JsReplace("location.href=$1", GURL("/title2.html"))));
1694 
1695   EXPECT_THAT(kill_waiter.Wait(),
1696               Optional(HasSubstr("Trust Token params in main frame nav")));
1697 }
1698 
1699 }  // namespace content
1700