Add tests for data access issue.

Bug: 6212665

Verify whether bug is properly fixed for all browsers.

Change-Id: I3ff53b8596831cbfc6a9d0fbe54337ea28eed97d
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index 856d348..131c03c 100755
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -17,6 +17,8 @@
 
 import libcore.io.Base64;
 import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
 import org.apache.http.HttpException;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
@@ -62,6 +64,7 @@
 import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.Hashtable;
 import java.util.List;
@@ -130,6 +133,7 @@
     private boolean mSsl;
     private MimeTypeMap mMap;
     private String mLastQuery;
+    private ArrayList<HttpEntity> mRequestEntities;
     private int mRequestCount;
     private long mDocValidity;
     private long mDocAge;
@@ -176,6 +180,7 @@
         } else {
             mServerUri = "http://localhost:" + SERVER_PORT;
         }
+        mRequestEntities = new ArrayList<HttpEntity>();
         mMap = MimeTypeMap.getSingleton();
         mServerThread = new ServerThread(this, mSsl);
         mServerThread.start();
@@ -389,6 +394,13 @@
         return mLastQuery;
     }
 
+    /**
+     * Returns all received request entities since the last reset.
+     */
+    public synchronized ArrayList<HttpEntity> getRequestEntities() {
+        return mRequestEntities;
+    }
+
     public synchronized int getRequestCount() {
         return mRequestCount;
     }
@@ -412,6 +424,16 @@
     }
 
     /**
+     * Resets the saved requests and request counts.
+     */
+    public synchronized void resetRequestState() {
+
+        mRequestCount = 0;
+        mLastQuery = null;
+        mRequestEntities = new ArrayList<HttpEntity>();
+    }
+
+    /**
      * Generate a response to the given request.
      * @throws InterruptedException
      * @throws IOException
@@ -419,12 +441,15 @@
     private HttpResponse getResponse(HttpRequest request) throws InterruptedException, IOException {
         RequestLine requestLine = request.getRequestLine();
         HttpResponse response = null;
-        Log.i(TAG, requestLine.getMethod() + ": " + requestLine.getUri());
         String uriString = requestLine.getUri();
+        Log.i(TAG, requestLine.getMethod() + ": " + uriString);
 
         synchronized (this) {
             mRequestCount += 1;
             mLastQuery = uriString;
+            if (request instanceof HttpEntityEnclosingRequest) {
+                mRequestEntities.add(((HttpEntityEnclosingRequest)request).getEntity());
+            }
         }
 
         URI uri = URI.create(uriString);
@@ -803,6 +828,10 @@
                     if (isShutdownRequest(request)) {
                         mIsCancelled = true;
                     }
+                    if (request instanceof HttpEntityEnclosingRequest) {
+                        conn.receiveRequestEntity( (HttpEntityEnclosingRequest) request);
+                    }
+
                     mExecutorService.submit(new HandleResponseTask(conn, request));
                 } catch (IOException e) {
                     // normal during shutdown, ignore
diff --git a/tests/tests/security/src/android/security/cts/BrowserTest.java b/tests/tests/security/src/android/security/cts/BrowserTest.java
index 7991933..223f83f 100644
--- a/tests/tests/security/src/android/security/cts/BrowserTest.java
+++ b/tests/tests/security/src/android/security/cts/BrowserTest.java
@@ -17,6 +17,7 @@
 package android.security.cts;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -24,21 +25,16 @@
 import android.test.AndroidTestCase;
 import android.webkit.cts.CtsTestServer;
 
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.http.HttpEntity;
 /**
- * Test for browsers which share state across multiple javascript intents.
- * Such browsers may be vulnerable to a data stealing attack.
- *
- * In particular, this test detects CVE-2011-2357.  Patches for CVE-2011-2357
- * are available at:
- *
- * http://android.git.kernel.org/?p=platform/packages/apps/Browser.git;a=commit;h=afa4ab1e4c1d645e34bd408ce04cadfd2e5dae1e
- * http://android.git.kernel.org/?p=platform/packages/apps/Browser.git;a=commit;h=096bae248453abe83cbb2e5a2c744bd62cdb620b
- *
- * See also: http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-2357
+ * Test file for browser security issues.
  */
 public class BrowserTest extends AndroidTestCase {
     private CtsTestServer mWebServer;
@@ -57,7 +53,8 @@
 
     /**
      * Verify that no state is preserved across multiple intents sent
-     * to the browser when we reuse a browser tab.
+     * to the browser when we reuse a browser tab. If such data is preserved,
+     * then browser is vulnerable to a data stealing attack.
      *
      * In this test, we send two intents to the Android browser. The first
      * intent sets document.b2 to 1.  The second intent attempts to read
@@ -66,6 +63,12 @@
      *
      * If state is preserved across browser tabs, we ask
      * the browser to send an HTTP request to our local server.
+     *
+     * See http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-2357 for
+     * vulnerability information for this test case.
+     *
+     * See commits  096bae248453abe83cbb2e5a2c744bd62cdb620b and
+     * afa4ab1e4c1d645e34bd408ce04cadfd2e5dae1e for patches for above vulnerability.
      */
     public void testTabReuse() throws InterruptedException {
         List<Intent> intents = getAllJavascriptIntents();
@@ -88,7 +91,8 @@
 
     /**
      * Verify that no state is preserved across multiple intents sent
-     * to the browser when we run out of usable browser tabs.
+     * to the browser when we run out of usable browser tabs.  If such data is
+     * preserved, then browser is vulnerable to a data stealing attack.
      *
      * In this test, we send 20 intents to the Android browser.  Each
      * intent sets the variable "document.b1" equal to 1.  If we are able
@@ -97,6 +101,12 @@
      * to the local server, recording this fact.
      *
      * Our test fails if the local server ever receives an HTTP request.
+     *
+     * See http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-2357 for
+     * vulnerability information this test case.
+     *
+     * See commits  096bae248453abe83cbb2e5a2c744bd62cdb620b and
+     * afa4ab1e4c1d645e34bd408ce04cadfd2e5dae1e for patches for above vulnerability.
      */
     public void testTabExhaustion() throws InterruptedException {
         List<Intent> intents = getAllJavascriptIntents();
@@ -125,6 +135,81 @@
     }
 
     /**
+     * See Bug 6212665 for detailed information about this issue.
+     */
+    public void testBrowserPrivateDataAccess() throws Throwable {
+
+        // Create a list of all intents for http display. This includes all browsers.
+        List<Intent> intents = createAllIntents(Uri.parse("http://www.google.com"));
+        String action = "\"" + mWebServer.getBaseUri() + "/\"";
+        // test each browser
+        for (Intent intent : intents) {
+            // reset state
+            mWebServer.resetRequestState();
+            // define target file, which is supposedly protected from this app
+            String targetFile = "file://" + getTargetFilePath();
+            String html =
+                "<html><body>\n" +
+                "  <form name=\"myform\" action=" + action + " method=\"post\">\n" +
+                "  <input type='text' name='val'/>\n" +
+                "  <a href=\"javascript :submitform()\">Search</a></form>\n" +
+                "<script>\n" +
+                "  var client = new XMLHttpRequest();\n" +
+                "  client.open('GET', '" + targetFile + "');\n" +
+                "  client.onreadystatechange = function() {\n" +
+                "  if(client.readyState == 4) {\n" +
+                "    myform.val.value = client.responseText;\n" +
+                "    document.myform.submit(); \n" +
+                "  }}\n" +
+                "  client.send();\n" +
+                "</script></body></html>\n";
+            String filename = "jsfileaccess.html";
+            // create a local HTML to access protected file
+            FileOutputStream out = mContext.openFileOutput(filename,
+                                                           mContext.MODE_WORLD_READABLE);
+            Writer writer = new OutputStreamWriter(out, "UTF-8");
+            writer.write(html);
+            writer.flush();
+            writer.close();
+
+            String filepath = mContext.getFileStreamPath(filename).getAbsolutePath();
+            Uri uri = Uri.parse("file://" + filepath);
+            // do a file request
+            intent.setData(uri);
+            mContext.startActivity(intent);
+            /*
+             * Wait 5 seconds for the browser to contact the server, but
+             * fail fast if we detect the bug
+             */
+            for (int j = 0; j < 5; j++) {
+                // it seems that even when cross-origin policy prevents a file
+                // access, browser is still doing a POST sometimes, but it just
+                // sends the query part and no private data. Make sure this does not
+                // cause a false alarm.
+                if (mWebServer.getRequestEntities().size() > 0) {
+                    int len = 0;
+                    for (HttpEntity entity : mWebServer.getRequestEntities()) {
+                        len += entity.getContentLength();
+                    }
+                    final int queryLen = "val=".length();
+                    assertTrue("Failed preventing access to private data", len <= queryLen);
+                }
+                Thread.sleep(1000);
+            }
+        }
+    }
+
+    private String getTargetFilePath() throws Exception {
+        FileOutputStream out = mContext.openFileOutput("target.txt",
+                                                       mContext.MODE_WORLD_READABLE);
+        Writer writer = new OutputStreamWriter(out, "UTF-8");
+        writer.write("testing");
+        writer.flush();
+        writer.close();
+        return mContext.getFileStreamPath("target.txt").getAbsolutePath();
+    }
+
+    /**
      * This method returns a List of explicit Intents for all programs
      * which handle javascript URIs.
      */
@@ -145,7 +230,14 @@
                 + "document.location=\"" + localServerUri + "\""
                 + "};"
                 + varName + "=1";
-        Uri uri = Uri.parse(javascript);
+
+        return createAllIntents(Uri.parse(javascript));
+    }
+
+    /**
+     * Create intents for all activities that can display the given URI.
+     */
+    private List<Intent> createAllIntents(Uri uri) {
 
         Intent implicit = new Intent(Intent.ACTION_VIEW);
         implicit.setData(uri);