Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1625)

Unified Diff: content/browser/loader/cross_site_resource_handler_browsertest.cc

Issue 1384113002: CrossSiteResourceHandler: cancel request if the RFH is gone (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@no_isolate_apps
Patch Set: Add EXPECT_TRUE's Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « content/browser/loader/cross_site_resource_handler.cc ('k') | content/content_tests.gypi » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: content/browser/loader/cross_site_resource_handler_browsertest.cc
diff --git a/content/browser/loader/cross_site_resource_handler_browsertest.cc b/content/browser/loader/cross_site_resource_handler_browsertest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4d66c1a9812275603c130aec7420c5b0d091de3a
--- /dev/null
+++ b/content/browser/loader/cross_site_resource_handler_browsertest.cc
@@ -0,0 +1,277 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/frame_messages.h"
+#include "content/public/browser/resource_dispatcher_host.h"
+#include "content/public/browser/resource_dispatcher_host_delegate.h"
+#include "content/public/browser/resource_throttle.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_resource_dispatcher_host_delegate.h"
+#include "ipc/ipc_security_test_util.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+namespace {
+
+// A ResourceDispatchHostDelegate that uses ResourceThrottles to pause a
+// targeted request temporarily, to run a chunk of test code.
+class TestResourceDispatcherHostDelegate
+ : public ShellResourceDispatcherHostDelegate {
+ public:
+ using RequestDeferredHook = base::Callback<void(const base::Closure& resume)>;
+ TestResourceDispatcherHostDelegate() : throttle_created_(false) {}
+
+ void RequestBeginning(net::URLRequest* request,
+ ResourceContext* resource_context,
+ AppCacheService* appcache_service,
+ ResourceType resource_type,
+ ScopedVector<ResourceThrottle>* throttles) override {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ShellResourceDispatcherHostDelegate::RequestBeginning(
+ request, resource_context, appcache_service, resource_type, throttles);
+
+ // If this is a request for the tracked URL, add a throttle to track it.
+ if (request->url() == tracked_url_) {
+ // Expect only a single request for the tracked url.
+ ASSERT_FALSE(throttle_created_);
+ throttle_created_ = true;
+
+ throttles->push_back(
+ new CallbackRunningResourceThrottle(request, this, run_on_start_));
+ }
+ }
+
+ // Starts tracking a URL. The request for previously tracked URL, if any,
+ // must have been made and deleted before calling this function.
+ void SetTrackedURL(const GURL& tracked_url,
+ const RequestDeferredHook& run_on_start) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Should not currently be tracking any URL.
+ ASSERT_FALSE(run_loop_);
+
+ // Create a RunLoop that will be stopped once the request for the tracked
+ // URL has been destroyed, to allow tracking the URL while also waiting for
+ // other events.
+ run_loop_.reset(new base::RunLoop());
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&TestResourceDispatcherHostDelegate::SetTrackedURLOnIOThread,
+ base::Unretained(this), tracked_url, run_on_start,
+ run_loop_->QuitClosure()));
+ }
+
+ // Waits until the tracked URL has been requested, and the request for it has
+ // been destroyed.
+ bool WaitForTrackedURLAndGetCompleted() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ run_loop_->Run();
+ run_loop_.reset();
+ return tracked_request_completed_;
+ }
+
+ private:
+ // A ResourceThrottle which defers the request at WillStartRequest time until
+ // a test-supplied callback completes. Notifies |tracker| when the request is
+ // destroyed.
+ class CallbackRunningResourceThrottle : public ResourceThrottle {
+ public:
+ CallbackRunningResourceThrottle(net::URLRequest* request,
+ TestResourceDispatcherHostDelegate* tracker,
+ const RequestDeferredHook& run_on_start)
+ : request_(request),
+ tracker_(tracker),
+ run_on_start_(run_on_start),
+ weak_factory_(this) {}
+
+ void WillStartRequest(bool* defer) override {
+ *defer = true;
+ base::Closure resume_request_on_io_thread = base::Bind(
+ base::IgnoreResult(&BrowserThread::PostTask), BrowserThread::IO,
+ FROM_HERE, base::Bind(&CallbackRunningResourceThrottle::Resume,
+ weak_factory_.GetWeakPtr()));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(run_on_start_, resume_request_on_io_thread));
+ }
+
+ ~CallbackRunningResourceThrottle() override {
+ // If the request is deleted without being cancelled, its status will
+ // indicate it succeeded, so have to check if the request is still pending
+ // as well.
+ tracker_->OnTrackedRequestDestroyed(!request_->is_pending() &&
+ request_->status().is_success());
+ }
+
+ // ResourceThrottle implementation:
+ const char* GetNameForLogging() const override {
+ return "CallbackRunningResourceThrottle";
+ }
+
+ private:
+ void Resume() { controller()->Resume(); }
+ net::URLRequest* request_;
+ TestResourceDispatcherHostDelegate* tracker_;
+ RequestDeferredHook run_on_start_;
+ base::WeakPtrFactory<CallbackRunningResourceThrottle> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackRunningResourceThrottle);
+ };
+
+ void SetTrackedURLOnIOThread(const GURL& tracked_url,
+ const RequestDeferredHook& run_on_start,
+ const base::Closure& run_loop_quit_closure) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ throttle_created_ = false;
+ tracked_url_ = tracked_url;
+ run_on_start_ = run_on_start;
+ run_loop_quit_closure_ = run_loop_quit_closure;
+ }
+
+ void OnTrackedRequestDestroyed(bool completed) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ tracked_request_completed_ = completed;
+ tracked_url_ = GURL();
+ run_on_start_ = RequestDeferredHook();
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ run_loop_quit_closure_);
+ }
+
+ // These live on the IO thread.
+ GURL tracked_url_;
+ bool throttle_created_;
+ base::Closure run_loop_quit_closure_;
+ RequestDeferredHook run_on_start_;
+
+ // This lives on the UI thread.
+ scoped_ptr<base::RunLoop> run_loop_;
+
+ // Set on the IO thread while |run_loop_| is non-nullptr, read on the UI
+ // thread after deleting run_loop_.
+ bool tracked_request_completed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcherHostDelegate);
+};
+
+class CrossSiteResourceHandlerTest : public ContentBrowserTest {
+ public:
+ CrossSiteResourceHandlerTest() : old_delegate_(nullptr) {}
+
+ // ContentBrowserTest implementation:
+ void SetUpOnMainThread() override {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &CrossSiteResourceHandlerTest::InjectResourceDispatcherHostDelegate,
+ base::Unretained(this)));
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+ content::SetupCrossSiteRedirector(embedded_test_server());
+ }
+
+ void TearDownOnMainThread() override {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&CrossSiteResourceHandlerTest::
+ RestoreResourceDispatcherHostDelegate,
+ base::Unretained(this)));
+ }
+
+ protected:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ IsolateAllSitesForTesting(command_line);
+ }
+
+ void InjectResourceDispatcherHostDelegate() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ old_delegate_ = ResourceDispatcherHostImpl::Get()->delegate();
+ ResourceDispatcherHostImpl::Get()->SetDelegate(&tracking_delegate_);
+ }
+
+ void RestoreResourceDispatcherHostDelegate() {
+ DCHECK_CURRENTLY_ON(BrowserThread::IO);
+ ResourceDispatcherHostImpl::Get()->SetDelegate(old_delegate_);
+ old_delegate_ = nullptr;
+ }
+
+ TestResourceDispatcherHostDelegate& tracking_delegate() {
+ return tracking_delegate_;
+ }
+
+ private:
+ TestResourceDispatcherHostDelegate tracking_delegate_;
+ ResourceDispatcherHostDelegate* old_delegate_;
+};
+
+void SimulateMaliciousFrameDetachOnUIThread(int render_process_id,
+ int frame_routing_id,
+ const base::Closure& done_cb) {
+ RenderFrameHostImpl* rfh =
+ RenderFrameHostImpl::FromID(render_process_id, frame_routing_id);
+ CHECK(rfh);
+
+ // Inject a frame detach message. An attacker-controlled renderer could do
+ // this without also cancelling the pending navigation (as blink would, if you
+ // removed the iframe from the document via js).
+ rfh->OnMessageReceived(FrameHostMsg_Detach(frame_routing_id));
+ done_cb.Run();
+}
+
+} // namespace
+
+// Regression test for https://crbug.com/538784 -- ensures that one can't
+// sidestep CrossSiteResourceHandler by detaching a frame mid-request.
+IN_PROC_BROWSER_TEST_F(CrossSiteResourceHandlerTest,
+ NoDeliveryToDetachedFrame) {
+ GURL attacker_page = embedded_test_server()->GetURL(
+ "evil.com", "/cross_site_iframe_factory.html?evil(evil)");
+ EXPECT_TRUE(NavigateToURL(shell(), attacker_page));
+
+ FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+ ->GetFrameTree()
+ ->root();
+
+ RenderFrameHost* child_frame = root->child_at(0)->current_frame_host();
+
+ // Attacker initiates a navigation to a cross-site document. Under --site-per-
+ // process, these bytes must not be sent to the attacker process.
+ GURL target_resource =
+ embedded_test_server()->GetURL("a.com", "/title1.html");
+
+ // We add a testing hook to simulate the attacker-controlled process sending
+ // FrameHostMsg_Detach before the http response arrives. At the time this test
+ // was written, the resource request had a lifetime separate from the RFH,
+ tracking_delegate().SetTrackedURL(
+ target_resource, base::Bind(&SimulateMaliciousFrameDetachOnUIThread,
+ child_frame->GetProcess()->GetID(),
+ child_frame->GetRoutingID()));
+ EXPECT_TRUE(ExecuteScript(
+ shell()->web_contents()->GetMainFrame(),
+ base::StringPrintf("document.getElementById('child-0').src='%s'",
+ target_resource.spec().c_str())));
+
+ // Wait for the scenario to play out. If this returns false, it means the
+ // request did not succeed, which is good in this case.
+ EXPECT_FALSE(tracking_delegate().WaitForTrackedURLAndGetCompleted())
+ << "Request should have been cancelled before reaching the renderer.";
+}
+
+} // namespace content
« no previous file with comments | « content/browser/loader/cross_site_resource_handler.cc ('k') | content/content_tests.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698