[android_webview] Fix UAF in request interception code.

Cherry-pick http://crrev.com/61653004

> It was possible for any of the tasks posted by the
> AndroidStreamReaderURLRequestJob to access the InterceptedRequestData
> after the URLRequest owning that data structure was deleted
> The fix is to make the newly created job's Delgate own the
> InterceptedRequestData since the AndroidStreamReaderURLRequestJob takes
> care to not delete the Delegate before all async tasks have finished.
>
> BUG=internal b/11520856
> TEST=AndroidWebViewTest
> Android-only CL, trybots happy.
> NOTRY=true
>
> Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=233937

BUG: 11520856
Change-Id: I2063f9e13aecd24091d5f5d9361473b3656fd752
diff --git a/android_webview/browser/aw_request_interceptor.cc b/android_webview/browser/aw_request_interceptor.cc
index 0e5e283..72673d2 100644
--- a/android_webview/browser/aw_request_interceptor.cc
+++ b/android_webview/browser/aw_request_interceptor.cc
@@ -24,27 +24,7 @@
 
 namespace {
 
-const void* kURLRequestUserDataKey = &kURLRequestUserDataKey;
-
-class URLRequestUserData : public base::SupportsUserData::Data {
- public:
-    URLRequestUserData(
-        scoped_ptr<InterceptedRequestData> intercepted_request_data)
-        : intercepted_request_data_(intercepted_request_data.Pass()) {
-    }
-
-    static URLRequestUserData* Get(net::URLRequest* request) {
-      return reinterpret_cast<URLRequestUserData*>(
-          request->GetUserData(kURLRequestUserDataKey));
-    }
-
-    const InterceptedRequestData* intercepted_request_data() const {
-      return intercepted_request_data_.get();
-    }
-
- private:
-  scoped_ptr<InterceptedRequestData> intercepted_request_data_;
-};
+const void* kRequestAlreadyQueriedDataKey = &kRequestAlreadyQueriedDataKey;
 
 } // namespace
 
@@ -82,26 +62,23 @@
   // request.
   // This is done not only for efficiency reasons, but also for correctness
   // as it is possible for the Interceptor chain to be invoked more than once
-  // (in which case we don't want to query the embedder multiple times).
-  URLRequestUserData* user_data = URLRequestUserData::Get(request);
+  // in which case we don't want to query the embedder multiple times.
+  // Note: The Interceptor chain is not invoked more than once if we create a
+  // URLRequestJob in this method, so this is only caching negative hits.
+  if (request->GetUserData(kRequestAlreadyQueriedDataKey))
+    return NULL;
+  request->SetUserData(kRequestAlreadyQueriedDataKey,
+                       new base::SupportsUserData::Data());
 
-  if (!user_data) {
-    // To ensure we only query the embedder once, we rely on the fact that the
-    // user_data object will be created and attached to the URLRequest after a
-    // call to QueryForInterceptedRequestData is made (regardless of whether
-    // the result of that call is a valid InterceptedRequestData* pointer or
-    // NULL.
-    user_data = new URLRequestUserData(
-        QueryForInterceptedRequestData(request->url(), request));
-    request->SetUserData(kURLRequestUserDataKey, user_data);
-  }
-
-  const InterceptedRequestData* intercepted_request_data =
-      user_data->intercepted_request_data();
+  scoped_ptr<InterceptedRequestData> intercepted_request_data =
+      QueryForInterceptedRequestData(request->url(), request);
 
   if (!intercepted_request_data)
     return NULL;
-  return intercepted_request_data->CreateJobFor(request, network_delegate);
+
+  // The newly created job will own the InterceptedRequestData.
+  return InterceptedRequestData::CreateJobFor(
+      intercepted_request_data.Pass(), request, network_delegate);
 }
 
 } // namespace android_webview
diff --git a/android_webview/browser/intercepted_request_data.h b/android_webview/browser/intercepted_request_data.h
index 672833c..c4e01c3 100644
--- a/android_webview/browser/intercepted_request_data.h
+++ b/android_webview/browser/intercepted_request_data.h
@@ -26,10 +26,11 @@
   // This creates a URLRequestJob for the |request| wich will read data from
   // the |intercepted_request_data| structure (instead of going to the network
   // or to the cache).
-  // The newly created job does not take ownership of |this|.
-  virtual net::URLRequestJob* CreateJobFor(
+  // The newly created job takes ownership of |intercepted_request_data|.
+  static net::URLRequestJob* CreateJobFor(
+      scoped_ptr<InterceptedRequestData> intercepted_request_data,
       net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) const = 0;
+      net::NetworkDelegate* network_delegate);
 
  protected:
   InterceptedRequestData() {}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
index 8e4cef5..83db1db 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
@@ -25,7 +25,9 @@
 import java.io.InputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
 import java.util.List;
 import java.util.Random;
 
@@ -255,6 +257,70 @@
         mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount);
     }
 
+    private static class SlowInterceptedRequestData extends InterceptedRequestData {
+        private CallbackHelper mReadStartedCallbackHelper = new CallbackHelper();
+        private CountDownLatch mLatch = new CountDownLatch(1);
+
+        public SlowInterceptedRequestData(String mimeType, String encoding, InputStream data) {
+            super(mimeType, encoding, data);
+        }
+
+        @Override
+        public InputStream getData() {
+            mReadStartedCallbackHelper.notifyCalled();
+            try {
+                mLatch.await();
+            } catch (InterruptedException e) {
+                // ignore
+            }
+            return super.getData();
+        }
+
+        public void unblockReads() {
+            mLatch.countDown();
+        }
+
+        public CallbackHelper getReadStartedCallbackHelper() {
+            return mReadStartedCallbackHelper;
+        }
+    }
+
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testDoesNotCrashOnSlowStream() throws Throwable {
+        final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
+        final String aboutPageData = makePageWithTitle("some title");
+        final String encoding = "UTF-8";
+        final SlowInterceptedRequestData slowInterceptedRequestData =
+            new SlowInterceptedRequestData("text/html", encoding,
+                    new ByteArrayInputStream(aboutPageData.getBytes(encoding)));
+
+        mShouldInterceptRequestHelper.setReturnValue(slowInterceptedRequestData);
+        int callCount = slowInterceptedRequestData.getReadStartedCallbackHelper().getCallCount();
+        loadUrlAsync(mAwContents, aboutPageUrl);
+        slowInterceptedRequestData.getReadStartedCallbackHelper().waitForCallback(callCount);
+
+        // Now the AwContents is "stuck" waiting for the SlowInputStream to finish reading so we
+        // delete it to make sure that the dangling 'read' task doesn't cause a crash. Unfortunately
+        // this will not always lead to a crash but it should happen often enough for us to notice.
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().removeAllViews();
+            }
+        });
+        destroyAwContentsOnMainSync(mAwContents);
+        pollOnUiThread(new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return AwContents.getNativeInstanceCount() == 0;
+            }
+        });
+
+        slowInterceptedRequestData.unblockReads();
+    }
+
     @SmallTest
     @Feature({"AndroidWebView"})
     public void testHttpStatusField() throws Throwable {
diff --git a/android_webview/native/intercepted_request_data_impl.cc b/android_webview/native/intercepted_request_data_impl.cc
index dc5fe22..03b9f42 100644
--- a/android_webview/native/intercepted_request_data_impl.cc
+++ b/android_webview/native/intercepted_request_data_impl.cc
@@ -22,8 +22,8 @@
     public AndroidStreamReaderURLRequestJob::Delegate {
  public:
     StreamReaderJobDelegateImpl(
-        const InterceptedRequestDataImpl* intercepted_request_data)
-        : intercepted_request_data_impl_(intercepted_request_data) {
+        scoped_ptr<InterceptedRequestDataImpl> intercepted_request_data)
+        : intercepted_request_data_impl_(intercepted_request_data.Pass()) {
       DCHECK(intercepted_request_data_impl_);
     }
 
@@ -53,11 +53,31 @@
     }
 
  private:
-    const InterceptedRequestDataImpl* intercepted_request_data_impl_;
+    scoped_ptr<InterceptedRequestDataImpl> intercepted_request_data_impl_;
 };
 
 } // namespace
 
+// static
+net::URLRequestJob* InterceptedRequestData::CreateJobFor(
+    scoped_ptr<InterceptedRequestData> intercepted_request_data,
+    net::URLRequest* request,
+    net::NetworkDelegate* network_delegate) {
+  DCHECK(intercepted_request_data);
+  DCHECK(request);
+  DCHECK(network_delegate);
+
+  return new AndroidStreamReaderURLRequestJob(
+      request,
+      network_delegate,
+      scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate>(
+          new StreamReaderJobDelegateImpl(
+              // PassAs rightfully doesn't support downcasts.
+              scoped_ptr<InterceptedRequestDataImpl>(
+                  static_cast<InterceptedRequestDataImpl*>(
+                      intercepted_request_data.release())))));
+}
+
 InterceptedRequestDataImpl::InterceptedRequestDataImpl(
     const base::android::JavaRef<jobject>& obj)
   : java_object_(obj) {
@@ -99,13 +119,4 @@
   return RegisterNativesImpl(env);
 }
 
-net::URLRequestJob* InterceptedRequestDataImpl::CreateJobFor(
-    net::URLRequest* request,
-    net::NetworkDelegate* network_delegate) const {
-  scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate>
-      stream_reader_job_delegate_impl(new StreamReaderJobDelegateImpl(this));
-  return new AndroidStreamReaderURLRequestJob(
-      request, network_delegate, stream_reader_job_delegate_impl.Pass());
-}
-
 } // namespace android_webview
diff --git a/android_webview/native/intercepted_request_data_impl.h b/android_webview/native/intercepted_request_data_impl.h
index 457bda9..0246569 100644
--- a/android_webview/native/intercepted_request_data_impl.h
+++ b/android_webview/native/intercepted_request_data_impl.h
@@ -25,10 +25,6 @@
   virtual bool GetMimeType(JNIEnv* env, std::string* mime_type) const;
   virtual bool GetCharset(JNIEnv* env, std::string* charset) const;
 
-  virtual net::URLRequestJob* CreateJobFor(
-      net::URLRequest* request,
-      net::NetworkDelegate* network_delegate) const OVERRIDE;
-
  private:
   base::android::ScopedJavaGlobalRef<jobject> java_object_;