• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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