[M85 Dev][EFL] Fix errors to generate ninja files
[platform/framework/web/chromium-efl.git] / chrome / browser / chrome_security_exploit_browsertest.cc
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 "base/bind.h"
6 #include "base/command_line.h"
7 #include "base/macros.h"
8 #include "base/memory/ptr_util.h"
9 #include "base/run_loop.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/test/metrics/histogram_tester.h"
12 #include "base/test/scoped_feature_list.h"
13 #include "chrome/browser/extensions/extension_browsertest.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_commands.h"
16 #include "chrome/browser/ui/singleton_tabs.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/test/base/ui_test_utils.h"
19 #include "content/public/browser/blob_handle.h"
20 #include "content/public/browser/notification_observer.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/notification_types.h"
23 #include "content/public/browser/render_frame_host.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/site_isolation_policy.h"
26 #include "content/public/browser/web_contents_observer.h"
27 #include "content/public/common/content_switches.h"
28 #include "content/public/common/url_constants.h"
29 #include "content/public/test/browser_test.h"
30 #include "content/public/test/browser_test_utils.h"
31 #include "extensions/common/extension_urls.h"
32 #include "mojo/public/cpp/bindings/pending_remote.h"
33 #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
34 #include "net/dns/mock_host_resolver.h"
35 #include "net/test/embedded_test_server/embedded_test_server.h"
36 #include "storage/browser/blob/blob_registry_impl.h"
37 #include "third_party/blink/public/common/blob/blob_utils.h"
38 #include "third_party/blink/public/common/features.h"
39 #include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h"
40 #include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
41
42 // The goal of these tests is to "simulate" exploited renderer processes, which
43 // can send arbitrary IPC messages and confuse browser process internal state,
44 // leading to security bugs. We are trying to verify that the browser doesn't
45 // perform any dangerous operations in such cases.
46 // This is similar to the security_exploit_browsertest.cc tests, but also
47 // includes chrome/ layer concepts such as extensions.
48 class ChromeSecurityExploitBrowserTest
49     : public extensions::ExtensionBrowserTest {
50  public:
51   ChromeSecurityExploitBrowserTest() {}
52   ~ChromeSecurityExploitBrowserTest() override {}
53
54   void SetUpOnMainThread() override {
55     extensions::ExtensionBrowserTest::SetUpOnMainThread();
56
57     ASSERT_TRUE(embedded_test_server()->Start());
58     host_resolver()->AddRule("*", "127.0.0.1");
59
60     extension_ = LoadExtension(test_data_dir_.AppendASCII("simple_with_icon"));
61   }
62
63   const extensions::Extension* extension() { return extension_; }
64
65   std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
66       const std::string& contents,
67       const std::string& content_type) {
68     std::unique_ptr<content::BlobHandle> result;
69     base::RunLoop loop;
70     content::BrowserContext::CreateMemoryBackedBlob(
71         profile(), base::as_bytes(base::make_span(contents)), content_type,
72         base::BindOnce(
73             [](std::unique_ptr<content::BlobHandle>* out_blob,
74                base::OnceClosure done,
75                std::unique_ptr<content::BlobHandle> blob) {
76               *out_blob = std::move(blob);
77               std::move(done).Run();
78             },
79             &result, loop.QuitClosure()));
80     loop.Run();
81     EXPECT_TRUE(result);
82     return result;
83   }
84
85  private:
86   const extensions::Extension* extension_;
87
88   DISALLOW_COPY_AND_ASSIGN(ChromeSecurityExploitBrowserTest);
89 };
90
91 // Subclass of ChromeSecurityExploitBrowserTest that uses --disable-web-security
92 // to simulate an exploited renderer.  Note that this also disables some browser
93 // process checks, so it's not ideal for all exploit tests.
94 class ChromeWebSecurityDisabledBrowserTest
95     : public ChromeSecurityExploitBrowserTest {
96  public:
97   ChromeWebSecurityDisabledBrowserTest() {}
98   ~ChromeWebSecurityDisabledBrowserTest() override {}
99
100   void SetUpCommandLine(base::CommandLine* command_line) override {
101     ChromeSecurityExploitBrowserTest::SetUpCommandLine(command_line);
102     command_line->AppendSwitch(switches::kDisableWebSecurity);
103   }
104
105   DISALLOW_COPY_AND_ASSIGN(ChromeWebSecurityDisabledBrowserTest);
106 };
107
108 // TODO(nasko): This test as written is incompatible with Site Isolation
109 // restrictions, which disallow the cross-origin pushState call.
110 // Find a different way to implement issuing the illegal request or just
111 // delete the test if we have coverage elsewhere. See https://crbug.com/929161.
112 IN_PROC_BROWSER_TEST_F(ChromeWebSecurityDisabledBrowserTest,
113                        DISABLED_ChromeExtensionResources) {
114   // Load a page that requests a chrome-extension:// image through XHR. We
115   // expect this load to fail, as it is an illegal request.
116   GURL foo = embedded_test_server()->GetURL("foo.com",
117                                             "/chrome_extension_resource.html");
118
119   content::DOMMessageQueue msg_queue;
120
121   ui_test_utils::NavigateToURL(browser(), foo);
122
123   std::string status;
124   std::string expected_status("0");
125   EXPECT_TRUE(msg_queue.WaitForMessage(&status));
126   EXPECT_STREQ(status.c_str(), expected_status.c_str());
127 }
128
129 // Tests that a normal web process cannot send a commit for a Chrome Web Store
130 // URL.  See https://crbug.com/172119.
131 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
132                        CommitWebStoreURLInWebProcess) {
133   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
134
135   content::WebContents* web_contents =
136       browser()->tab_strip_model()->GetActiveWebContents();
137   content::RenderFrameHost* rfh = web_contents->GetMainFrame();
138
139   // This IPC should result in a kill because the Chrome Web Store is not
140   // allowed to commit in |rfh->GetProcess()|.
141   base::HistogramTester histograms;
142   content::RenderProcessHostWatcher crash_observer(
143       rfh->GetProcess(),
144       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
145
146   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
147   // to commit in any process.
148   GURL blank_url = GURL(url::kAboutBlankURL);
149   GURL webstore_url = extension_urls::GetWebstoreLaunchURL();
150   content::PwnCommitIPC(web_contents, blank_url, webstore_url,
151                         url::Origin::Create(GURL(webstore_url)));
152   web_contents->GetController().LoadURL(
153       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
154
155   // If the process is killed in CanCommitURL, this test passes.
156   crash_observer.Wait();
157   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
158 }
159
160 // Tests that a non-extension process cannot send a commit of a blank URL with
161 // an extension origin.
162 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
163                        CommitExtensionOriginInWebProcess) {
164   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
165
166   content::WebContents* web_contents =
167       browser()->tab_strip_model()->GetActiveWebContents();
168   content::RenderFrameHost* rfh = web_contents->GetMainFrame();
169
170   // This IPC should result in a kill because |ext_origin| is not allowed to
171   // commit in |rfh->GetProcess()|.
172   base::HistogramTester histograms;
173   content::RenderProcessHostWatcher crash_observer(
174       rfh->GetProcess(),
175       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
176
177   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
178   // to commit in any process.
179   GURL blank_url = GURL(url::kAboutBlankURL);
180   std::string ext_origin = "chrome-extension://" + extension()->id();
181   content::PwnCommitIPC(web_contents, blank_url, blank_url,
182                         url::Origin::Create(GURL(ext_origin)));
183   web_contents->GetController().LoadURL(
184       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
185
186   // If the process is killed in CanCommitOrigin, this test passes.
187   crash_observer.Wait();
188   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 114,
189                                 1);
190 }
191
192 // Tests that a non-extension process cannot send a commit of an extension URL.
193 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
194                        CommitExtensionURLInWebProcess) {
195   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
196
197   content::WebContents* web_contents =
198       browser()->tab_strip_model()->GetActiveWebContents();
199   content::RenderFrameHost* rfh = web_contents->GetMainFrame();
200
201   // This IPC should result in a kill because extension URLs are not allowed to
202   // commit in |rfh->GetProcess()|.
203   base::HistogramTester histograms;
204   content::RenderProcessHostWatcher crash_observer(
205       rfh->GetProcess(),
206       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
207
208   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
209   // to commit in any process.
210   GURL blank_url = GURL(url::kAboutBlankURL);
211   std::string ext_origin = "chrome-extension://" + extension()->id();
212   content::PwnCommitIPC(web_contents, blank_url, GURL(ext_origin),
213                         url::Origin::Create(GURL(ext_origin)));
214   web_contents->GetController().LoadURL(
215       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
216
217   // If the process is killed in CanCommitURL, this test passes.
218   crash_observer.Wait();
219   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
220 }
221
222 // Tests that a non-extension process cannot send a commit of an extension
223 // filesystem URL.
224 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
225                        CommitExtensionFilesystemURLInWebProcess) {
226   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
227
228   content::WebContents* web_contents =
229       browser()->tab_strip_model()->GetActiveWebContents();
230   content::RenderFrameHost* rfh = web_contents->GetMainFrame();
231
232   // This IPC should result in a kill because extension filesystem URLs are not
233   // allowed to commit in |rfh->GetProcess()|.
234   base::HistogramTester histograms;
235   content::RenderProcessHostWatcher crash_observer(
236       rfh->GetProcess(),
237       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
238
239   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
240   // to commit in any process.
241   GURL blank_url = GURL(url::kAboutBlankURL);
242   std::string ext_origin = "chrome-extension://" + extension()->id();
243   content::PwnCommitIPC(web_contents, blank_url,
244                         GURL("filesystem:" + ext_origin + "/foo"),
245                         url::Origin::Create(GURL(ext_origin)));
246   web_contents->GetController().LoadURL(
247       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
248
249   // If the process is killed in CanCommitURL, this test passes.
250   crash_observer.Wait();
251   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
252 }
253
254 // chrome://xyz should not be able to create a "filesystem:chrome://abc"
255 // resource.
256 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
257                        CreateFilesystemURLInOtherChromeUIOrigin) {
258   ui_test_utils::NavigateToURL(browser(), GURL("chrome://version"));
259
260   content::RenderFrameHost* rfh =
261       browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
262
263   // Block the renderer on operation that never completes, to shield it from
264   // receiving unexpected browser->renderer IPCs that might CHECK.
265   rfh->ExecuteJavaScriptWithUserGestureForTests(
266       base::ASCIIToUTF16("var r = new XMLHttpRequest();"
267                          "r.open('GET', '/slow?99999', false);"
268                          "r.send(null);"
269                          "while (1);"));
270
271   std::string payload = "<p>Hello world!</p>";
272   std::string payload_type = "text/html";
273
274   // Target an extension.
275   std::string target_origin = "chrome://downloads";
276
277   // Set up a blob ID and populate it with the attacker-controlled payload. This
278   // is just using the blob APIs directly since creating arbitrary blobs is not
279   // what is prohibited; this data is not in any origin.
280   std::unique_ptr<content::BlobHandle> blob =
281       CreateMemoryBackedBlob(payload, payload_type);
282   std::string blob_id = blob->GetUUID();
283
284   // Note: a well-behaved renderer would always send the following message here,
285   // but it's actually not necessary for the original attack to succeed, so we
286   // omit it. As a result there are some log warnings from the quota observer.
287   //
288   // IPC::IpcSecurityTestUtil::PwnMessageReceived(
289   //     rfh->GetProcess()->GetChannel(),
290   //     FileSystemHostMsg_OpenFileSystem(22, GURL(target_origin),
291   //                                      storage::kFileSystemTypeTemporary));
292
293   GURL target_url =
294       GURL("filesystem:" + target_origin + "/temporary/exploit.html");
295
296   content::PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url,
297                                               false, false, false);
298
299   // Write the blob into the file. If successful, this places an
300   // attacker-controlled value in a resource on the extension origin.
301   content::PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url,
302                                              blob_id, 0);
303
304   // Now navigate to |target_url| in a new tab. It should not contain |payload|.
305   AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED);
306   EXPECT_FALSE(content::WaitForLoadStop(
307       browser()->tab_strip_model()->GetWebContentsAt(0)));
308   rfh = browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
309
310   // If the attack is unsuccessful, the navigation ends up in an error
311   // page.
312   if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
313           !rfh->GetParent())) {
314     EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
315               rfh->GetSiteInstance()->GetSiteURL());
316   } else {
317     EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
318   }
319   std::string body;
320   std::string script = R"(
321     var textContent = document.body.innerText.replace(/\n+/g, '\n');
322     window.domAutomationController.send(textContent);
323   )";
324
325   EXPECT_TRUE(content::ExecuteScriptAndExtractString(rfh, script, &body));
326   EXPECT_EQ(
327       "Your file was not found\n"
328       "It may have been moved or deleted.\n"
329       "ERR_FILE_NOT_FOUND",
330       body);
331 }
332
333 // Extension isolation prevents a normal renderer process from being able to
334 // create a "filesystem:chrome-extension://sdgkjaghsdg/temporary/" resource.
335 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
336                        CreateFilesystemURLInExtensionOrigin) {
337   GURL page_url =
338       embedded_test_server()->GetURL("a.root-servers.net", "/title1.html");
339   ui_test_utils::NavigateToURL(browser(), page_url);
340
341   content::RenderFrameHost* rfh =
342       browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
343
344   // Block the renderer on operation that never completes, to shield it from
345   // receiving unexpected browser->renderer IPCs that might CHECK.
346   rfh->ExecuteJavaScriptWithUserGestureForTests(
347       base::ASCIIToUTF16("var r = new XMLHttpRequest();"
348                          "r.open('GET', '/slow?99999', false);"
349                          "r.send(null);"
350                          "while (1);"));
351
352   // JS code that the attacker would like to run in an extension process.
353   std::string payload = "<html><body>pwned.</body></html>";
354   std::string payload_type = "text/html";
355
356   // Target an extension.
357   std::string target_origin = "chrome-extension://" + extension()->id();
358
359   // Set up a blob ID and populate it with the attacker-controlled payload. This
360   // is just using the blob APIs directly since creating arbitrary blobs is not
361   // what is prohibited; this data is not in any origin.
362   std::unique_ptr<content::BlobHandle> blob =
363       CreateMemoryBackedBlob(payload, payload_type);
364   std::string blob_id = blob->GetUUID();
365
366   // Note: a well-behaved renderer would always call Open first before calling
367   // Create and Write, but it's actually not necessary for the original attack
368   // to succeed, so we omit it. As a result there are some log warnings from the
369   // quota observer.
370
371   GURL target_url =
372       GURL("filesystem:" + target_origin + "/temporary/exploit.html");
373
374   content::PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url,
375                                               false, false, false);
376
377   // Write the blob into the file. If successful, this places an
378   // attacker-controlled value in a resource on the extension origin.
379   content::PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url,
380                                              blob_id, 0);
381
382   // Now navigate to |target_url| in a new tab. It should not contain |payload|.
383   AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED);
384   EXPECT_FALSE(content::WaitForLoadStop(
385       browser()->tab_strip_model()->GetWebContentsAt(0)));
386   rfh = browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
387
388   // If the attack is unsuccessful, the navigation ends up in an error
389   // page.
390   if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
391           !rfh->GetParent())) {
392     EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
393               rfh->GetSiteInstance()->GetSiteURL());
394   } else {
395     EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
396   }
397   std::string body;
398   std::string script = R"(
399     var textContent = document.body.innerText.replace(/\n+/g, '\n');
400     window.domAutomationController.send(textContent);
401   )";
402
403   EXPECT_TRUE(content::ExecuteScriptAndExtractString(rfh, script, &body));
404   EXPECT_EQ(
405       "Your file was not found\n"
406       "It may have been moved or deleted.\n"
407       "ERR_FILE_NOT_FOUND",
408       body);
409 }
410
411 namespace {
412
413 class BlobURLStoreInterceptor
414     : public blink::mojom::BlobURLStoreInterceptorForTesting {
415  public:
416   static void Intercept(
417       GURL target_url,
418       mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>
419           receiver) {
420     auto interceptor =
421         base::WrapUnique(new BlobURLStoreInterceptor(target_url));
422     auto* raw_interceptor = interceptor.get();
423     auto impl = receiver->SwapImplForTesting(std::move(interceptor));
424     raw_interceptor->url_store_ = std::move(impl);
425   }
426
427   blink::mojom::BlobURLStore* GetForwardingInterface() override {
428     return url_store_.get();
429   }
430
431   void Register(mojo::PendingRemote<blink::mojom::Blob> blob,
432                 const GURL& url,
433                 RegisterCallback callback) override {
434     GetForwardingInterface()->Register(std::move(blob), target_url_,
435                                        std::move(callback));
436   }
437
438  private:
439   explicit BlobURLStoreInterceptor(GURL target_url) : target_url_(target_url) {}
440
441   std::unique_ptr<blink::mojom::BlobURLStore> url_store_;
442   GURL target_url_;
443 };
444
445 }  // namespace
446
447 class ChromeSecurityExploitBrowserTestMojoBlobURLs
448     : public ChromeSecurityExploitBrowserTest {
449  public:
450   ChromeSecurityExploitBrowserTestMojoBlobURLs() = default;
451
452   void TearDown() override {
453     storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr);
454   }
455 };
456
457 // Extension isolation prevents a normal renderer process from being able to
458 // create a "blob:chrome-extension://" resource.
459 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
460                        CreateBlobInExtensionOrigin) {
461   // Target an extension.
462   std::string target_origin = "chrome-extension://" + extension()->id();
463   std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
464   auto intercept_hook =
465       base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
466                           GURL("blob:" + target_origin + "/" + blob_path));
467   storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
468
469   ui_test_utils::NavigateToURL(
470       browser(),
471       embedded_test_server()->GetURL("a.root-servers.net", "/title1.html"));
472
473   content::RenderFrameHost* rfh =
474       browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
475
476   content::RenderProcessHostBadMojoMessageWaiter crash_observer(
477       rfh->GetProcess());
478
479   // The renderer should always get killed, but sometimes ExecuteScript returns
480   // true anyway, so just ignore the result.
481   ignore_result(
482       content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))"));
483
484   // If the process is killed, this test passes.
485   EXPECT_EQ(
486       "Received bad user message: Non committable URL passed to "
487       "BlobURLStore::Register",
488       crash_observer.Wait());
489 }
490
491 // chrome://xyz should not be able to create a "blob:chrome://abc" resource.
492 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
493                        CreateBlobInOtherChromeUIOrigin) {
494   ui_test_utils::NavigateToURL(browser(), GURL("chrome://version"));
495
496   // All these are attacker controlled values.
497   std::string blob_type = "text/html";
498   std::string blob_contents = "<p>Hello world!</p>";
499   std::string blob_path = "f7dfbeb5-8e41-4c4a-8486-a52fed33c4c0";
500
501   // Target an extension.
502   std::string target_origin = "chrome://downloads";
503
504   auto intercept_hook =
505       base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
506                           GURL("blob:" + target_origin + "/" + blob_path));
507   storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
508
509   content::RenderFrameHost* rfh =
510       browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
511
512   content::RenderProcessHostBadMojoMessageWaiter crash_observer(
513       rfh->GetProcess());
514
515   // The renderer should always get killed, but sometimes ExecuteScript returns
516   // true anyway, so just ignore the result.
517   ignore_result(
518       content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))"));
519
520   // If the process is killed, this test passes.
521   EXPECT_EQ(
522       "Received bad user message: Non committable URL passed to "
523       "BlobURLStore::Register",
524       crash_observer.Wait());
525 }