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);