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