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