AppSecurity: Add traffic stats test, and fix file access test
* Fix the private file access test which would fail because the path
was wrong.
* Add a test that ensures the private file is actually "not accessible"
because it can't be as opposed to it not being there: the new test
accesses a public file created at the same time as the private file.
* Add tests around traffic stats
. add internet permission to app that creates data.
. generate private traffic stats (tagged sockets).
. read back traffic stats to make sure that only public stats are visible.
Bug: 10349057
Change-Id: Ic444185bccb05cf31849ba3c986aed5d3de90303
Signed-off-by: JP Abgrall <jpa@google.com>
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
index 8a44dfa..40d3cff 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
@@ -16,21 +16,24 @@
package com.android.cts.appaccessdata;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.IOException;
import android.test.AndroidTestCase;
/**
- * Test that another app's private data cannot be accessed.
+ * Test that another app's private data cannot be accessed, while its public data can.
*
- * Assumes that {@link APP_WITH_DATA_PKG} has already created the private data.
+ * Assumes that {@link APP_WITH_DATA_PKG} has already created the private and public data.
*/
public class AccessPrivateDataTest extends AndroidTestCase {
/**
- * The Android package name of the application that owns the private data
+ * The Android package name of the application that owns the data
*/
private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata";
@@ -39,9 +42,15 @@
* {@link APP_WITH_DATA_PKG}.
*/
private static final String PRIVATE_FILE_NAME = "private_file.txt";
+ /**
+ * Name of public file to access. This must match the name of the file created by
+ * {@link APP_WITH_DATA_PKG}.
+ */
+ private static final String PUBLIC_FILE_NAME = "public_file.txt";
/**
- * Tests that another app's private file cannot be accessed
+ * Tests that another app's private data cannot be accessed. It includes file
+ * and detailed traffic stats.
* @throws IOException
*/
public void testAccessPrivateData() throws IOException {
@@ -58,5 +67,60 @@
} catch (SecurityException e) {
// also valid
}
+ accessPrivateTrafficStats();
+ }
+
+ /**
+ * Tests that another app's public file can be accessed
+ * @throws IOException
+ */
+ public void testAccessPublicData() throws IOException {
+ try {
+ getOtherAppUid();
+ } catch (FileNotFoundException e) {
+ fail("Was not able to access another app's public file: " + e);
+ } catch (SecurityException e) {
+ fail("Was not able to access another app's public file: " + e);
+ }
+ }
+
+ private int getOtherAppUid() throws IOException, FileNotFoundException, SecurityException {
+ // construct the absolute file path to the other app's public file
+ String publicFilePath = String.format("/data/data/%s/files/%s", APP_WITH_DATA_PKG,
+ PUBLIC_FILE_NAME);
+ DataInputStream inputStream = new DataInputStream(new FileInputStream(publicFilePath));
+ int otherAppUid = (int)inputStream.readInt();
+ inputStream.close();
+ return otherAppUid;
+ }
+
+ private void accessPrivateTrafficStats() throws IOException {
+ int otherAppUid = -1;
+ try {
+ otherAppUid = getOtherAppUid();
+ } catch (FileNotFoundException e) {
+ fail("Was not able to access another app's public file: " + e);
+ } catch (SecurityException e) {
+ fail("Was not able to access another app's public file: " + e);
+ }
+
+ boolean foundOtherStats = false;
+ try {
+ BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats"));
+ String line;
+ while ((line = qtaguidReader.readLine()) != null) {
+ String tokens[] = line.split(" ");
+ if (tokens.length > 3 && tokens[3].equals(String.valueOf(otherAppUid))) {
+ foundOtherStats = true;
+ if (!tokens[2].equals("0x0")) {
+ fail("Other apps detailed traffic stats leaked");
+ }
+ }
+ }
+ qtaguidReader.close();
+ } catch (FileNotFoundException e) {
+ fail("Was not able to access qtaguid/stats: " + e);
+ }
+ assertTrue("Was expecting to find other apps' traffic stats", foundOtherStats);
}
}
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
index 4b10030..9decbcd 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
@@ -22,6 +22,7 @@
access.
-->
+ <uses-permission android:name="android.permission.INTERNET" />
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java
index 1de6464..e11681a 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/CreatePrivateDataTest.java
@@ -22,10 +22,23 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.net.TrafficStats;
import android.test.AndroidTestCase;
+import android.util.Log;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
/**
* Test that will create private app data.
@@ -36,9 +49,15 @@
public class CreatePrivateDataTest extends AndroidTestCase {
/**
+ * The Android package name of the application that owns the private data
+ */
+ private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata";
+
+ /**
* Name of private file to create.
*/
private static final String PRIVATE_FILE_NAME = "private_file.txt";
+ private static final String PUBLIC_FILE_NAME = "public_file.txt";
private static final String PREFERENCES_FILE_NAME = "preferences";
private static final String PREFERENCE_KEY = "preference_key";
@@ -49,7 +68,8 @@
static final String DB_VALUE = "test_value";
/**
- * Creates a file private to this app
+ * Creates the private data for this app, which includes
+ * file, database entries, and traffic stats.
* @throws IOException if any error occurred when creating the file
*/
public void testCreatePrivateData() throws IOException {
@@ -59,8 +79,34 @@
outputStream.close();
assertTrue(getContext().getFileStreamPath(PRIVATE_FILE_NAME).exists());
+ outputStream = getContext().openFileOutput(PUBLIC_FILE_NAME,
+ Context.MODE_WORLD_READABLE);
+ DataOutputStream dataOut = new DataOutputStream(outputStream);
+ dataOut.writeInt(getContext().getApplicationInfo().uid);
+ dataOut.close();
+ outputStream.close();
+ // Ensure that some file will be accessible via the same path that will be used by other app.
+ accessPublicData();
+
writeToPreferences();
writeToDatabase();
+ createTrafficStatsWithTags();
+ }
+
+ private void accessPublicData() throws IOException {
+ try {
+ // construct the absolute file path to the app's public's file the same
+ // way as the appaccessdata package will.
+ String publicFilePath = String.format("/data/data/%s/files/%s", APP_WITH_DATA_PKG,
+ PUBLIC_FILE_NAME);
+ DataInputStream inputStream = new DataInputStream(new FileInputStream(publicFilePath));
+ int otherAppUid = (int)inputStream.readInt();
+ inputStream.close();
+ } catch (FileNotFoundException e) {
+ fail("Was not able to access own public file: " + e);
+ } catch (SecurityException e) {
+ fail("Was not able to access own public file: " + e);
+ }
}
private void writeToPreferences() {
@@ -127,6 +173,76 @@
}
}
+ private void accessOwnTrafficStats() throws IOException {
+ final int ownAppUid = getContext().getApplicationInfo().uid;
+
+ boolean foundOwnDetailedStats = false;
+ try {
+ BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats"));
+ String line;
+ while ((line = qtaguidReader.readLine()) != null) {
+ String tokens[] = line.split(" ");
+ if (tokens.length > 3 && tokens[3].equals(String.valueOf(ownAppUid))) {
+ if (!tokens[2].equals("0x0")) {
+ foundOwnDetailedStats = true;
+ }
+ }
+ }
+ qtaguidReader.close();
+ } catch (FileNotFoundException e) {
+ fail("Was not able to access qtaguid/stats: " + e);
+ }
+ assertTrue("Was expecting to find own traffic stats", foundOwnDetailedStats);
+ }
+
+ private void createTrafficStatsWithTags() throws IOException {
+
+ // Transfer 1MB of data across an explicitly localhost socket.
+ final int byteCount = 1024;
+ final int packetCount = 1024;
+
+ final ServerSocket server = new ServerSocket(0);
+ new Thread("CreatePrivateDataTest.createTrafficStatsWithTags") {
+ @Override
+ public void run() {
+ try {
+ Socket socket = new Socket("localhost", server.getLocalPort());
+ // Make sure that each write()+flush() turns into a packet:
+ // disable Nagle.
+ socket.setTcpNoDelay(true);
+ OutputStream out = socket.getOutputStream();
+ byte[] buf = new byte[byteCount];
+ for (int i = 0; i < packetCount; i++) {
+ TrafficStats.setThreadStatsTag(i % 10);
+ TrafficStats.tagSocket(socket);
+ out.write(buf);
+ out.flush();
+ }
+ out.close();
+ socket.close();
+ } catch (IOException e) {
+ assertTrue("io exception" + e, false);
+ }
+ }
+ }.start();
+
+ try {
+ Socket socket = server.accept();
+ InputStream in = socket.getInputStream();
+ byte[] buf = new byte[byteCount];
+ int read = 0;
+ while (read < byteCount * packetCount) {
+ int n = in.read(buf);
+ assertTrue("Unexpected EOF", n > 0);
+ read += n;
+ }
+ } finally {
+ server.close();
+ }
+
+ accessOwnTrafficStats();
+ }
+
static class TestDatabaseOpenHelper extends SQLiteOpenHelper {
static final String _ID = "_id";