1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.appaccessdata; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.fail; 22 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.net.TrafficStats; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.os.ParcelFileDescriptor; 31 import android.os.SystemClock; 32 import android.system.ErrnoException; 33 import android.system.Os; 34 import android.system.OsConstants; 35 36 import androidx.test.InstrumentationRegistry; 37 import androidx.test.runner.AndroidJUnit4; 38 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 import java.io.File; 44 import java.io.FileInputStream; 45 import java.io.FileNotFoundException; 46 import java.io.IOException; 47 import java.net.InetSocketAddress; 48 import java.net.Socket; 49 50 /** 51 * Test that another app's private data cannot be accessed, while its public data can. 52 * 53 * These tests are for apps targeting SDK 29 (or lower). Tests for the behavior for apps targeting 54 * SDK 30+ are in {@code AppDataIsolationTests}. 55 * 56 * Assumes that {@link #APP_WITH_DATA_PKG} has already created the private and public data. 57 */ 58 @RunWith(AndroidJUnit4.class) 59 public class AccessPrivateDataTest { 60 61 /** 62 * The Android package name of the application that owns the data 63 */ 64 private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata"; 65 66 /** 67 * Name of private file to access. This must match the name of the file created by 68 * {@link #APP_WITH_DATA_PKG}. 69 */ 70 private static final String PRIVATE_FILE_NAME = "private_file.txt"; 71 /** 72 * Name of public file to access. This must match the name of the file created by 73 * {@link #APP_WITH_DATA_PKG}. 74 */ 75 private static final String PUBLIC_FILE_NAME = "public_file.txt"; 76 77 private static final String QTAGUID_STATS_FILE = "/proc/net/xt_qtaguid/stats"; 78 79 private static final Uri PRIVATE_TARGET = Uri.parse("content://com.android.cts.appwithdata/"); 80 81 private Context mContext; 82 83 @Before setUp()84 public void setUp() { 85 mContext = InstrumentationRegistry.getContext(); 86 } 87 88 /** 89 * Tests that another app's private data cannot be accessed. 90 */ 91 @Test testAccessPrivateData()92 public void testAccessPrivateData() throws IOException { 93 try { 94 // construct the absolute file path to the app's private file 95 ApplicationInfo applicationInfo = getApplicationInfo(APP_WITH_DATA_PKG); 96 File privateFile = new File(applicationInfo.dataDir, "files/" + PRIVATE_FILE_NAME); 97 FileInputStream inputStream = new FileInputStream(privateFile); 98 inputStream.read(); 99 inputStream.close(); 100 fail("Was able to access another app's private data"); 101 } catch (FileNotFoundException | SecurityException e) { 102 // expected 103 } 104 } 105 106 /** 107 * Tests that another app's public file cannot be accessed 108 */ 109 @Test testAccessPublicData()110 public void testAccessPublicData() throws IOException { 111 try { 112 // construct the absolute file path to the other app's public file 113 ApplicationInfo applicationInfo = getApplicationInfo(APP_WITH_DATA_PKG); 114 File publicFile = new File(applicationInfo.dataDir, "files/" + PUBLIC_FILE_NAME); 115 FileInputStream inputStream = new FileInputStream(publicFile); 116 inputStream.read(); 117 inputStream.close(); 118 fail("Was able to access another app's public file"); 119 } catch (FileNotFoundException | SecurityException e) { 120 // expected 121 } 122 } 123 124 /** 125 * Tests that we can't even access another app's root private data dir. 126 */ 127 @Test testStatPrivateDataDir()128 public void testStatPrivateDataDir() { 129 ApplicationInfo applicationInfo = getApplicationInfo(APP_WITH_DATA_PKG); 130 String path = applicationInfo.dataDir; 131 try { 132 Os.stat(path); 133 fail("Was able to stat() another app's private data dir: " + path); 134 } catch (ErrnoException expected) { 135 assertEquals(path, OsConstants.EACCES, expected.errno); 136 } 137 } 138 139 @Test testAccessProcQtaguidTrafficStatsFailed()140 public void testAccessProcQtaguidTrafficStatsFailed() { 141 // For untrusted app with SDK P or above, proc/net/xt_qtaguid files are no long readable. 142 // They can only read their own stats from TrafficStats API. The test for TrafficStats API 143 // will ensure they cannot read other apps stats. 144 assertFalse("untrusted app should not be able to read qtaguid profile", 145 new File(QTAGUID_STATS_FILE).canRead()); 146 } 147 148 @Test testAccessPrivateTrafficStats()149 public void testAccessPrivateTrafficStats() { 150 int otherAppUid = -1; 151 try { 152 otherAppUid = mContext 153 .createPackageContext(APP_WITH_DATA_PKG, 0 /*flags*/) 154 .getApplicationInfo().uid; 155 } catch (NameNotFoundException e) { 156 fail("Was not able to find other app"); 157 } 158 159 final int UNSUPPORTED = -1; 160 assertEquals(UNSUPPORTED, TrafficStats.getUidRxBytes(otherAppUid)); 161 assertEquals(UNSUPPORTED, TrafficStats.getUidRxPackets(otherAppUid)); 162 assertEquals(UNSUPPORTED, TrafficStats.getUidTxBytes(otherAppUid)); 163 assertEquals(UNSUPPORTED, TrafficStats.getUidTxPackets(otherAppUid)); 164 } 165 166 @Test testTrafficStatsStatsUidSelf()167 public void testTrafficStatsStatsUidSelf() throws Exception { 168 final boolean isInstant = Boolean.parseBoolean( 169 InstrumentationRegistry.getArguments().getString("is_instant")); 170 // test not applicable for instant applications; they cannot shift blame 171 if (isInstant) { 172 return; 173 } 174 final int uid = android.os.Process.myUid(); 175 final long rxb = TrafficStats.getUidRxBytes(uid); 176 final long rxp = TrafficStats.getUidRxPackets(uid); 177 final long txb = TrafficStats.getUidTxBytes(uid); 178 final long txp = TrafficStats.getUidTxPackets(uid); 179 180 // Start remote server 181 final int port = mContext.getContentResolver().call(PRIVATE_TARGET, "start", null, null) 182 .getInt("port"); 183 184 // Try talking to them, but shift blame 185 try { 186 final Socket socket = new Socket(); 187 socket.setTcpNoDelay(true); 188 189 Bundle extras = new Bundle(); 190 extras.putParcelable("fd", ParcelFileDescriptor.fromSocket(socket)); 191 mContext.getContentResolver().call(PRIVATE_TARGET, "tag", null, extras); 192 193 socket.connect(new InetSocketAddress("localhost", port)); 194 195 socket.getOutputStream().write(42); 196 socket.getOutputStream().flush(); 197 final int val = socket.getInputStream().read(); 198 assertEquals(42, val); 199 socket.close(); 200 } catch (IOException e) { 201 throw new RuntimeException(e); 202 } finally { 203 mContext.getContentResolver().call(PRIVATE_TARGET, "stop", null, null); 204 } 205 206 SystemClock.sleep(1000); 207 208 // Since we shifted blame, our stats shouldn't have changed 209 assertEquals(rxb, TrafficStats.getUidRxBytes(uid)); 210 assertEquals(rxp, TrafficStats.getUidRxPackets(uid)); 211 assertEquals(txb, TrafficStats.getUidTxBytes(uid)); 212 assertEquals(txp, TrafficStats.getUidTxPackets(uid)); 213 } 214 getApplicationInfo(String packageName)215 private ApplicationInfo getApplicationInfo(String packageName) { 216 try { 217 return mContext.getPackageManager().getApplicationInfo(packageName, 0); 218 } catch (PackageManager.NameNotFoundException e) { 219 throw new IllegalStateException("Expected package not found: " + e); 220 } 221 } 222 } 223