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

Side by Side 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: fixes from nasko 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 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/callback.h"
6 #include "base/command_line.h"
7 #include "base/memory/weak_ptr.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/thread_task_runner_handle.h"
10 #include "content/browser/frame_host/render_frame_host_impl.h"
11 #include "content/browser/loader/resource_dispatcher_host_impl.h"
12 #include "content/browser/web_contents/web_contents_impl.h"
13 #include "content/common/frame_messages.h"
14 #include "content/public/browser/resource_dispatcher_host.h"
15 #include "content/public/browser/resource_dispatcher_host_delegate.h"
16 #include "content/public/browser/resource_throttle.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/test/browser_test_utils.h"
19 #include "content/public/test/content_browser_test.h"
20 #include "content/public/test/content_browser_test_utils.h"
21 #include "content/public/test/test_utils.h"
22 #include "content/shell/browser/shell.h"
23 #include "content/shell/browser/shell_resource_dispatcher_host_delegate.h"
24 #include "ipc/ipc_security_test_util.h"
25 #include "net/dns/mock_host_resolver.h"
26 #include "net/test/embedded_test_server/embedded_test_server.h"
27 #include "net/url_request/url_request.h"
28
29 namespace content {
30
31 namespace {
32
33 // A ResourceDispatchHostDelegate that uses ResourceThrottles to pause a
34 // targeted request temporarily, to run a chunk of test code.
35 class TestResourceDispatcherHostDelegate
36 : public ShellResourceDispatcherHostDelegate {
37 public:
38 using RequestDeferredHook = base::Callback<void(const base::Closure& resume)>;
39 TestResourceDispatcherHostDelegate() : throttle_created_(false) {}
40
41 void RequestBeginning(net::URLRequest* request,
42 ResourceContext* resource_context,
43 AppCacheService* appcache_service,
44 ResourceType resource_type,
45 ScopedVector<ResourceThrottle>* throttles) override {
46 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
47 ShellResourceDispatcherHostDelegate::RequestBeginning(
48 request, resource_context, appcache_service, resource_type, throttles);
49
50 // If this is a request for the tracked URL, add a throttle to track it.
51 if (request->url() == tracked_url_) {
52 // Expect only a single request for the tracked url.
53 ASSERT_FALSE(throttle_created_);
54 throttle_created_ = true;
55
56 throttles->push_back(
57 new CallbackRunningResourceThrottle(request, this, run_on_start_));
58 }
59 }
60
61 // Starts tracking a URL. The request for previously tracked URL, if any,
62 // must have been made and deleted before calling this function.
63 void SetTrackedURL(const GURL& tracked_url,
64 const RequestDeferredHook& run_on_start) {
65 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
66 // Should not currently be tracking any URL.
67 ASSERT_FALSE(run_loop_);
68
69 // Create a RunLoop that will be stopped once the request for the tracked
70 // URL has been destroyed, to allow tracking the URL while also waiting for
71 // other events.
72 run_loop_.reset(new base::RunLoop());
73
74 BrowserThread::PostTask(
75 BrowserThread::IO, FROM_HERE,
76 base::Bind(&TestResourceDispatcherHostDelegate::SetTrackedURLOnIOThread,
77 base::Unretained(this), tracked_url, run_on_start,
78 run_loop_->QuitClosure()));
79 }
80
81 // Waits until the tracked URL has been requested, and the request for it has
82 // been destroyed.
83 bool WaitForTrackedURLAndGetCompleted() {
84 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85 run_loop_->Run();
86 run_loop_.reset();
87 return tracked_request_completed_;
88 }
89
90 private:
91 // A ResourceThrottle which defers the request at WillStartRequest time until
92 // a test-supplied callback completes. Notifies |tracker| when the request is
93 // destroyed.
94 class CallbackRunningResourceThrottle : public ResourceThrottle {
95 public:
96 CallbackRunningResourceThrottle(net::URLRequest* request,
97 TestResourceDispatcherHostDelegate* tracker,
98 const RequestDeferredHook& run_on_start)
99 : request_(request),
100 tracker_(tracker),
101 run_on_start_(run_on_start),
102 weak_factory_(this) {}
103
104 void WillStartRequest(bool* defer) override {
105 *defer = true;
106 base::Closure resume_request_on_io_thread = base::Bind(
107 base::IgnoreResult(&BrowserThread::PostTask), BrowserThread::IO,
108 FROM_HERE, base::Bind(&CallbackRunningResourceThrottle::Resume,
109 weak_factory_.GetWeakPtr()));
110 BrowserThread::PostTask(
111 BrowserThread::UI, FROM_HERE,
112 base::Bind(run_on_start_, resume_request_on_io_thread));
113 }
114
115 ~CallbackRunningResourceThrottle() override {
116 // If the request is deleted without being cancelled, its status will
117 // indicate it succeeded, so have to check if the request is still pending
118 // as well.
119 tracker_->OnTrackedRequestDestroyed(!request_->is_pending() &&
120 request_->status().is_success());
121 }
122
123 // ResourceThrottle implementation:
124 const char* GetNameForLogging() const override {
125 return "CallbackRunningResourceThrottle";
126 }
127
128 private:
129 void Resume() { controller()->Resume(); }
130 net::URLRequest* request_;
131 TestResourceDispatcherHostDelegate* tracker_;
132 RequestDeferredHook run_on_start_;
133 base::WeakPtrFactory<CallbackRunningResourceThrottle> weak_factory_;
134
135 DISALLOW_COPY_AND_ASSIGN(CallbackRunningResourceThrottle);
136 };
137
138 void SetTrackedURLOnIOThread(const GURL& tracked_url,
139 const RequestDeferredHook& run_on_start,
140 const base::Closure& run_loop_quit_closure) {
141 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
142 throttle_created_ = false;
143 tracked_url_ = tracked_url;
144 run_on_start_ = run_on_start;
145 run_loop_quit_closure_ = run_loop_quit_closure;
146 }
147
148 void OnTrackedRequestDestroyed(bool completed) {
149 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
150 tracked_request_completed_ = completed;
151 tracked_url_ = GURL();
152 run_on_start_ = RequestDeferredHook();
153
154 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
155 run_loop_quit_closure_);
156 }
157
158 // These live on the IO thread.
159 GURL tracked_url_;
160 bool throttle_created_;
161 base::Closure run_loop_quit_closure_;
162 RequestDeferredHook run_on_start_;
163
164 // This lives on the UI thread.
165 scoped_ptr<base::RunLoop> run_loop_;
166
167 // Set on the IO thread while |run_loop_| is non-nullptr, read on the UI
168 // thread after deleting run_loop_.
169 bool tracked_request_completed_;
170
171 DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcherHostDelegate);
172 };
173
174 class CrossSiteResourceHandlerTest : public ContentBrowserTest {
175 public:
176 CrossSiteResourceHandlerTest() : old_delegate_(nullptr) {}
177
178 // ContentBrowserTest implementation:
179 void SetUpOnMainThread() override {
180 BrowserThread::PostTask(
181 BrowserThread::IO, FROM_HERE,
182 base::Bind(
183 &CrossSiteResourceHandlerTest::InjectResourceDispatcherHostDelegate,
184 base::Unretained(this)));
185 host_resolver()->AddRule("*", "127.0.0.1");
186 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
187 content::SetupCrossSiteRedirector(embedded_test_server());
188 }
189
190 void TearDownOnMainThread() override {
191 BrowserThread::PostTask(
192 BrowserThread::IO, FROM_HERE,
193 base::Bind(&CrossSiteResourceHandlerTest::
194 RestoreResourceDispatcherHostDelegate,
195 base::Unretained(this)));
196 }
197
198 protected:
199 void SetUpCommandLine(base::CommandLine* command_line) override {
200 IsolateAllSitesForTesting(command_line);
201 }
202
203 void InjectResourceDispatcherHostDelegate() {
204 DCHECK_CURRENTLY_ON(BrowserThread::IO);
205 old_delegate_ = ResourceDispatcherHostImpl::Get()->delegate();
206 ResourceDispatcherHostImpl::Get()->SetDelegate(&tracking_delegate_);
207 }
208
209 void RestoreResourceDispatcherHostDelegate() {
210 DCHECK_CURRENTLY_ON(BrowserThread::IO);
211 ResourceDispatcherHostImpl::Get()->SetDelegate(old_delegate_);
212 old_delegate_ = nullptr;
213 }
214
215 TestResourceDispatcherHostDelegate& tracking_delegate() {
216 return tracking_delegate_;
217 }
218
219 private:
220 TestResourceDispatcherHostDelegate tracking_delegate_;
221 ResourceDispatcherHostDelegate* old_delegate_;
222 };
223
224 void SimulateMaliciousFrameDetachOnUIThread(int render_process_id,
225 int frame_routing_id,
226 const base::Closure& done_cb) {
227 RenderFrameHostImpl* rfh =
228 RenderFrameHostImpl::FromID(render_process_id, frame_routing_id);
229 CHECK(rfh);
230
231 // Inject a frame detach message. An attacker-controlled renderer could do
232 // this without also cancelling the pending navigation (as blink would, if you
233 // removed the iframe from the document via js).
234 rfh->OnMessageReceived(FrameHostMsg_Detach(frame_routing_id));
235 done_cb.Run();
236 }
237
238 } // namespace
239
240 // Regression test for https://crbug.com/538784 -- ensures that one can't
241 // sidestep CrossSiteResourceHandler by detaching a frame mid-request.
242 IN_PROC_BROWSER_TEST_F(CrossSiteResourceHandlerTest,
243 NoDeliveryToDetachedFrame) {
244 GURL attacker_page = embedded_test_server()->GetURL(
245 "evil.com", "/cross_site_iframe_factory.html?evil(evil)");
246 NavigateToURL(shell(), attacker_page);
nasko 2015/10/05 18:38:33 Oh, missed to notice that this needs to be wrapped
ncarter (slow) 2015/10/05 18:59:34 Done here, and everywhere in the other browsertest
247
248 FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
249 ->GetFrameTree()
250 ->root();
251
252 RenderFrameHost* child_frame = root->child_at(0)->current_frame_host();
253
254 // Attacker initiates a navigation to a cross-site document. Under --site-per-
255 // process, these bytes must not be sent to the attacker process.
256 GURL target_resource =
257 embedded_test_server()->GetURL("a.com", "/title1.html");
258
259 // We add a testing hook to simulate the attacker-controlled process sending
260 // FrameHostMsg_Detach before the http response arrives. At the time this test
261 // was written, the resource request had a lifetime separate from the RFH,
262 tracking_delegate().SetTrackedURL(
263 target_resource, base::Bind(&SimulateMaliciousFrameDetachOnUIThread,
264 child_frame->GetProcess()->GetID(),
265 child_frame->GetRoutingID()));
266 EXPECT_TRUE(ExecuteScript(
267 shell()->web_contents()->GetMainFrame(),
268 base::StringPrintf("document.getElementById('child-0').src='%s'",
269 target_resource.spec().c_str())));
270
271 // Wait for the scenario to play out. If this returns false, it means the
272 // request did not succeed, which is good in this case.
273 EXPECT_FALSE(tracking_delegate().WaitForTrackedURLAndGetCompleted())
274 << "Request should have been cancelled before reaching the renderer.";
275 }
276
277 } // namespace content
OLDNEW
« 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