1 /*
2  * Copyright (C) 2022 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.server.pm.test.appenumeration;
18 
19 import static android.Manifest.permission.CLEAR_APP_USER_DATA;
20 import static android.Manifest.permission.DELETE_PACKAGES;
21 import static android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS;
22 import static android.Manifest.permission.MOVE_PACKAGE;
23 import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
24 
25 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
26 
27 import static com.google.common.truth.Truth.assertThat;
28 
29 import static org.junit.Assert.assertThrows;
30 
31 import android.app.AppGlobals;
32 import android.app.Instrumentation;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.pm.IPackageDataObserver;
37 import android.content.pm.IPackageManager;
38 import android.content.pm.KeySet;
39 import android.content.pm.PackageManager;
40 import android.os.Process;
41 import android.os.RemoteException;
42 import android.os.UserHandle;
43 
44 import androidx.test.platform.app.InstrumentationRegistry;
45 
46 import com.android.bedstead.harrier.BedsteadJUnit4;
47 import com.android.bedstead.harrier.DeviceState;
48 import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
49 import com.android.bedstead.nene.users.UserReference;
50 import com.android.compatibility.common.util.PollingCheck;
51 import com.android.compatibility.common.util.TestUtils;
52 
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.ClassRule;
56 import org.junit.Rule;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 
60 import java.io.File;
61 import java.util.concurrent.atomic.AtomicInteger;
62 
63 /**
64  * Verify that app without holding the {@link android.Manifest.permission.INTERACT_ACROSS_USERS}
65  * can't detect the existence of another app in the different users on the device via the
66  * side channel attacks.
67  */
68 @EnsureHasSecondaryUser
69 @RunWith(BedsteadJUnit4.class)
70 public class CrossUserPackageVisibilityTests {
71     private static final String TEST_DATA_DIR = "/data/local/tmp/appenumerationtests";
72     private static final String CROSS_USER_TEST_PACKAGE_NAME =
73             "com.android.appenumeration.crossuserpackagevisibility";
74     private static final String SHARED_USER_TEST_PACKAGE_NAME =
75             "com.android.appenumeration.shareduid";
76     private static final String HAS_APPOP_PERMISSION_PACKAGE_NAME =
77             "com.android.appenumeration.hasappoppermission";
78 
79     private static final File CROSS_USER_TEST_APK_FILE =
80             new File(TEST_DATA_DIR, "AppEnumerationCrossUserPackageVisibilityTestApp.apk");
81     private static final File SHARED_USER_TEST_APK_FILE =
82             new File(TEST_DATA_DIR, "AppEnumerationSharedUserTestApp.apk");
83     private static final File HAS_APPOP_PERMISSION_APK_FILE =
84             new File(TEST_DATA_DIR, "AppEnumerationHasAppOpPermissionTestApp.apk");
85 
86     private static final String ACTION_CROSS_USER_TEST =
87             "com.android.appenumeration.action.CROSS_USER_TEST";
88     private static final String PERMISSION_REQUEST_INSTALL_PACKAGES =
89             "android.permission.REQUEST_INSTALL_PACKAGES";
90     private static final ComponentName TEST_ACTIVITY_COMPONENT_NAME = new ComponentName(
91             CROSS_USER_TEST_PACKAGE_NAME, "com.android.appenumeration.testapp.DummyActivity");
92 
93     private static final long DEFAULT_TIMEOUT_MS = 5000;
94 
95     @ClassRule
96     @Rule
97     public static final DeviceState sDeviceState = new DeviceState();
98 
99     private Instrumentation mInstrumentation;
100     private IPackageManager mIPackageManager;
101     private Context mContext;
102     private UserReference mCurrentUser;
103     private UserReference mOtherUser;
104 
105     @Before
setup()106     public void setup() {
107         mInstrumentation = InstrumentationRegistry.getInstrumentation();
108         mIPackageManager = AppGlobals.getPackageManager();
109         mContext = mInstrumentation.getContext();
110 
111         // Get another user
112         final UserReference primaryUser = sDeviceState.primaryUser();
113         if (primaryUser.id() == UserHandle.myUserId()) {
114             mCurrentUser = primaryUser;
115             mOtherUser = sDeviceState.secondaryUser();
116         } else {
117             mCurrentUser = sDeviceState.secondaryUser();
118             mOtherUser = primaryUser;
119         }
120 
121         uninstallPackage(CROSS_USER_TEST_PACKAGE_NAME);
122         uninstallPackage(SHARED_USER_TEST_PACKAGE_NAME);
123     }
124 
125     @After
tearDown()126     public void tearDown() {
127         uninstallPackage(CROSS_USER_TEST_PACKAGE_NAME);
128         uninstallPackage(SHARED_USER_TEST_PACKAGE_NAME);
129         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
130     }
131 
132     @Test
testGetSplashScreenTheme_withCrossUserId()133     public void testGetSplashScreenTheme_withCrossUserId() {
134         final int crossUserId = UserHandle.myUserId() + 1;
135         assertThrows(SecurityException.class,
136                 () -> mIPackageManager.getSplashScreenTheme(
137                         mInstrumentation.getContext().getPackageName(), crossUserId));
138     }
139 
140     @Test
testIsPackageSignedByKeySet_cannotDetectCrossUserPkg()141     public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception {
142         final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName());
143         assertThrows(IllegalArgumentException.class,
144                 () -> mIPackageManager.isPackageSignedByKeySet(
145                         CROSS_USER_TEST_PACKAGE_NAME, keySet));
146 
147         installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
148 
149         assertThrows(IllegalArgumentException.class,
150                 () -> mIPackageManager.isPackageSignedByKeySet(
151                         CROSS_USER_TEST_PACKAGE_NAME, keySet));
152     }
153 
154     @Test
testIsPackageSignedByKeySetExactly_cannotDetectCrossUserPkg()155     public void testIsPackageSignedByKeySetExactly_cannotDetectCrossUserPkg() throws Exception {
156         final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName());
157         assertThrows(IllegalArgumentException.class,
158                 () -> mIPackageManager.isPackageSignedByKeySetExactly(
159                         CROSS_USER_TEST_PACKAGE_NAME, keySet));
160 
161         installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
162 
163         assertThrows(IllegalArgumentException.class,
164                 () -> mIPackageManager.isPackageSignedByKeySetExactly(
165                         CROSS_USER_TEST_PACKAGE_NAME, keySet));
166     }
167 
168     @Test
testGetSigningKeySet_cannotDetectCrossUserPkg()169     public void testGetSigningKeySet_cannotDetectCrossUserPkg() {
170         final IllegalArgumentException e1 = assertThrows(IllegalArgumentException.class,
171                 () -> mIPackageManager.getSigningKeySet(CROSS_USER_TEST_PACKAGE_NAME));
172 
173         installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
174 
175         final IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class,
176                 () -> mIPackageManager.getSigningKeySet(CROSS_USER_TEST_PACKAGE_NAME));
177         assertThat(e1.getMessage()).isEqualTo(e2.getMessage());
178     }
179 
180     @Test
testGetKeySetByAlias_cannotDetectCrossUserPkg()181     public void testGetKeySetByAlias_cannotDetectCrossUserPkg() {
182         final String alias = CROSS_USER_TEST_PACKAGE_NAME + ".alias";
183         final IllegalArgumentException e1 = assertThrows(IllegalArgumentException.class,
184                 () -> mIPackageManager.getKeySetByAlias(CROSS_USER_TEST_PACKAGE_NAME, alias));
185 
186         installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
187 
188         final IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class,
189                 () -> mIPackageManager.getKeySetByAlias(CROSS_USER_TEST_PACKAGE_NAME, alias));
190         assertThat(e1.getMessage()).isEqualTo(e2.getMessage());
191     }
192 
193     @Test
testGetFlagsForUid_cannotDetectCrossUserPkg()194     public void testGetFlagsForUid_cannotDetectCrossUserPkg() throws Exception {
195         installPackage(CROSS_USER_TEST_APK_FILE);
196         final int uid = mContext.getPackageManager().getPackageUid(
197                 CROSS_USER_TEST_PACKAGE_NAME, PackageManager.PackageInfoFlags.of(0));
198 
199         uninstallPackageForUser(CROSS_USER_TEST_PACKAGE_NAME, mCurrentUser);
200 
201         assertThat(mIPackageManager.getFlagsForUid(uid)).isEqualTo(0);
202     }
203 
204     @Test
testGetUidForSharedUser_cannotDetectSharedUserPkg()205     public void testGetUidForSharedUser_cannotDetectSharedUserPkg() throws Exception {
206         assertThat(mIPackageManager.getUidForSharedUser(SHARED_USER_TEST_PACKAGE_NAME))
207                 .isEqualTo(Process.INVALID_UID);
208 
209         installPackageForUser(SHARED_USER_TEST_APK_FILE, mOtherUser, true /* forceQueryable */);
210 
211         assertThat(mIPackageManager.getUidForSharedUser(SHARED_USER_TEST_PACKAGE_NAME))
212                 .isEqualTo(Process.INVALID_UID);
213     }
214 
215     @Test
testClearApplicationUserData_cannotDetectStubPkg()216     public void testClearApplicationUserData_cannotDetectStubPkg() throws Exception {
217         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(CLEAR_APP_USER_DATA);
218         assertThat(clearApplicationUserData(CROSS_USER_TEST_PACKAGE_NAME)).isFalse();
219 
220         installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
221 
222         assertThat(clearApplicationUserData(CROSS_USER_TEST_PACKAGE_NAME)).isFalse();
223     }
224 
225     @Test
testGetBlockUninstallForUser_cannotDetectStubPkg()226     public void testGetBlockUninstallForUser_cannotDetectStubPkg() throws Exception {
227         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(DELETE_PACKAGES);
228         assertThat(mIPackageManager.setBlockUninstallForUser(
229                 CROSS_USER_TEST_PACKAGE_NAME, true, mCurrentUser.id())).isTrue();
230         try {
231             assertThat(mIPackageManager.getBlockUninstallForUser(
232                     CROSS_USER_TEST_PACKAGE_NAME, mCurrentUser.id())).isFalse();
233 
234             installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
235 
236             assertThat(mIPackageManager.getBlockUninstallForUser(
237                     CROSS_USER_TEST_PACKAGE_NAME, mCurrentUser.id())).isFalse();
238         } finally {
239             assertThat(mIPackageManager.setBlockUninstallForUser(
240                     CROSS_USER_TEST_PACKAGE_NAME, false, mCurrentUser.id())).isTrue();
241         }
242     }
243 
244     @Test
testMovePackage_cannotDetectStubPkg()245     public void testMovePackage_cannotDetectStubPkg() throws Exception {
246         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
247                 MOVE_PACKAGE, MOUNT_UNMOUNT_FILESYSTEMS);
248         assertThat(movePackage(CROSS_USER_TEST_PACKAGE_NAME, null /* volumeUuid */))
249                 .isEqualTo(MOVE_FAILED_DOESNT_EXIST);
250 
251         installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
252 
253         assertThat(movePackage(CROSS_USER_TEST_PACKAGE_NAME, null /* volumeUuid */))
254                 .isEqualTo(MOVE_FAILED_DOESNT_EXIST);
255     }
256 
257     @Test
testActivitySupportsIntentAsUser_cannotDetectStubPkg()258     public void testActivitySupportsIntentAsUser_cannotDetectStubPkg() throws Exception {
259         assertThat(mIPackageManager.activitySupportsIntentAsUser(
260                 TEST_ACTIVITY_COMPONENT_NAME,
261                 new Intent(ACTION_CROSS_USER_TEST),
262                 null,
263                 mCurrentUser.id())).isFalse();
264 
265         installPackageForUser(CROSS_USER_TEST_APK_FILE, mOtherUser);
266 
267         assertThat(mIPackageManager.activitySupportsIntentAsUser(
268                 TEST_ACTIVITY_COMPONENT_NAME,
269                 new Intent(ACTION_CROSS_USER_TEST),
270                 null,
271                 mCurrentUser.id())).isFalse();
272     }
273 
274     @Test
testGetAppOpPermissionPackages_cannotDetectPkg()275     public void testGetAppOpPermissionPackages_cannotDetectPkg() throws Exception {
276         final int userId = mCurrentUser.id();
277         assertThat(mIPackageManager
278                 .getAppOpPermissionPackages(PERMISSION_REQUEST_INSTALL_PACKAGES, userId))
279                 .asList().doesNotContain(HAS_APPOP_PERMISSION_PACKAGE_NAME);
280 
281         installPackageForUser(HAS_APPOP_PERMISSION_APK_FILE, mOtherUser, true /* forceQueryable */);
282 
283         assertThat(mIPackageManager
284                 .getAppOpPermissionPackages(PERMISSION_REQUEST_INSTALL_PACKAGES, userId))
285                 .asList().doesNotContain(HAS_APPOP_PERMISSION_PACKAGE_NAME);
286     }
287 
clearApplicationUserData(String packageName)288     private boolean clearApplicationUserData(String packageName) throws Exception {
289         final AtomicInteger result = new AtomicInteger(-1);
290         final IPackageDataObserver localObserver = new IPackageDataObserver.Stub() {
291             @Override
292             public void onRemoveCompleted(String removedPkgName, boolean succeeded)
293                     throws RemoteException {
294                 if (removedPkgName.equals(packageName)) {
295                     result.set(succeeded ? 1 : 0);
296                     result.notifyAll();
297                 }
298             }
299         };
300         mIPackageManager.clearApplicationUserData(packageName, localObserver, mCurrentUser.id());
301         TestUtils.waitOn(result, () -> result.get() != -1, DEFAULT_TIMEOUT_MS,
302                 "clearApplicationUserData: " + packageName);
303         return result.get() == 1;
304     }
305 
movePackage(String packageName, String volumeUuid)306     private int movePackage(String packageName, String volumeUuid) throws Exception {
307         final int moveId = mIPackageManager.movePackage(packageName, volumeUuid);
308         PollingCheck.check(
309                 "Waiting for the package " + packageName + " moving timeout",
310                 DEFAULT_TIMEOUT_MS,
311                 () -> PackageManager.isMoveStatusFinished(mIPackageManager.getMoveStatus(moveId)));
312         return mIPackageManager.getMoveStatus(moveId);
313     }
314 
installPackage(File apk)315     private static void installPackage(File apk) {
316         installPackageForUser(apk, null, false /* forceQueryable */);
317     }
318 
installPackageForUser(File apk, UserReference user)319     private static void installPackageForUser(File apk, UserReference user) {
320         installPackageForUser(apk, user, false /* forceQueryable */);
321     }
322 
installPackageForUser(File apk, UserReference user, boolean forceQueryable)323     private static void installPackageForUser(File apk, UserReference user,
324             boolean forceQueryable) {
325         assertThat(apk.exists()).isTrue();
326         final StringBuilder cmd = new StringBuilder("pm install -t ");
327         if (forceQueryable) {
328             cmd.append("--force-queryable ");
329         }
330         if (user != null) {
331             cmd.append("--user ").append(user.id()).append(" ");
332         }
333         cmd.append(apk.getPath());
334         final String result = runShellCommand(cmd.toString());
335         assertThat(result.trim()).contains("Success");
336     }
337 
uninstallPackage(String packageName)338     private static void uninstallPackage(String packageName) {
339         uninstallPackageForUser(packageName, null /* user */);
340     }
341 
uninstallPackageForUser(String packageName, UserReference user)342     private static void uninstallPackageForUser(String packageName, UserReference user) {
343         final StringBuilder cmd = new StringBuilder("pm uninstall ");
344         if (user != null) {
345             cmd.append("--user ").append(user.id()).append(" ");
346         }
347         cmd.append(packageName);
348         runShellCommand(cmd.toString());
349     }
350 }
351