1 /* 2 * Copyright (C) 2019 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 android.permission.cts; 18 19 import static android.Manifest.permission.ACCESS_FINE_LOCATION; 20 import static android.Manifest.permission.CAMERA; 21 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; 22 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; 23 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 24 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 25 26 import static com.android.compatibility.common.util.SystemUtil.eventually; 27 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 28 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; 29 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 30 31 import static org.junit.Assume.assumeFalse; 32 33 import android.app.ActivityManager; 34 import android.app.DreamManager; 35 import android.content.ComponentName; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.pm.PackageManager; 39 import android.platform.test.annotations.AsbSecurityTest; 40 import android.platform.test.rule.ScreenRecordRule; 41 import android.provider.DeviceConfig; 42 43 import androidx.test.filters.FlakyTest; 44 import androidx.test.platform.app.InstrumentationRegistry; 45 import androidx.test.uiautomator.By; 46 import androidx.test.uiautomator.UiDevice; 47 import androidx.test.uiautomator.UiObject2; 48 49 import com.android.compatibility.common.util.FeatureUtil; 50 import com.android.compatibility.common.util.SystemUtil; 51 import com.android.compatibility.common.util.UiAutomatorUtils2; 52 53 import org.junit.After; 54 import org.junit.Assert; 55 import org.junit.Before; 56 import org.junit.Ignore; 57 import org.junit.Rule; 58 import org.junit.Test; 59 60 import java.util.concurrent.CompletableFuture; 61 import java.util.concurrent.TimeUnit; 62 63 @ScreenRecordRule.ScreenRecord 64 public class OneTimePermissionTest { 65 66 private static final String APP_PKG_NAME = "android.permission.cts.appthatrequestpermission"; 67 private static final String CUSTOM_CAMERA_PERM_APP_PKG_NAME = 68 "android.permission.cts.appthatrequestcustomcamerapermission"; 69 private static final String APK = 70 "/data/local/tmp/cts-permission/CtsAppThatRequestsOneTimePermission.apk"; 71 private static final String CUSTOM_CAMERA_PERM_APK = 72 "/data/local/tmp/cts-permission/CtsAppThatRequestCustomCameraPermission.apk"; 73 private static final String EXTRA_FOREGROUND_SERVICE_LIFESPAN = 74 "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_LIFESPAN"; 75 private static final String EXTRA_FOREGROUND_SERVICE_STICKY = 76 "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_STICKY"; 77 78 public static final String CUSTOM_PERMISSION = "appthatrequestcustomcamerapermission.CUSTOM"; 79 80 private static final long ONE_TIME_TIMEOUT_MILLIS = 5000; 81 private static final long ONE_TIME_KILLED_DELAY_MILLIS = 5000; 82 private static final long ONE_TIME_TIMER_LOWER_GRACE_PERIOD = 1000; 83 private static final long ONE_TIME_TIMER_UPPER_GRACE_PERIOD = 10000; 84 85 private final Context mContext = 86 InstrumentationRegistry.getInstrumentation().getTargetContext(); 87 private final PackageManager mPackageManager = mContext.getPackageManager(); 88 private final UiDevice mUiDevice = 89 UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 90 private final ActivityManager mActivityManager = 91 mContext.getSystemService(ActivityManager.class); 92 private String mOldOneTimePermissionTimeoutValue; 93 private String mOldOneTimePermissionKilledDelayValue; 94 95 @Rule 96 public final ScreenRecordRule sScreenRecordRule = new ScreenRecordRule(false, false); 97 98 @Rule 99 public IgnoreAllTestsRule mIgnoreAutomotive = new IgnoreAllTestsRule( 100 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); 101 102 @Before wakeUpScreen()103 public void wakeUpScreen() { 104 SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP"); 105 106 SystemUtil.runShellCommand("input keyevent 82"); 107 } 108 109 @Before installApp()110 public void installApp() { 111 runShellCommandOrThrow("pm install -r " + APK); 112 runShellCommandOrThrow("pm install -r " + CUSTOM_CAMERA_PERM_APK); 113 } 114 115 @Before prepareDeviceForOneTime()116 public void prepareDeviceForOneTime() { 117 runWithShellPermissionIdentity(() -> { 118 mOldOneTimePermissionTimeoutValue = DeviceConfig.getProperty("permissions", 119 "one_time_permissions_timeout_millis"); 120 mOldOneTimePermissionKilledDelayValue = DeviceConfig.getProperty("permissions", 121 "one_time_permissions_killed_delay_millis"); 122 DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis", 123 Long.toString(ONE_TIME_TIMEOUT_MILLIS), false); 124 DeviceConfig.setProperty("permissions", 125 "one_time_permissions_killed_delay_millis", 126 Long.toString(ONE_TIME_KILLED_DELAY_MILLIS), false); 127 }); 128 } 129 130 @After uninstallApp()131 public void uninstallApp() { 132 runShellCommand("pm uninstall " + APP_PKG_NAME); 133 runShellCommand("pm uninstall " + CUSTOM_CAMERA_PERM_APP_PKG_NAME); 134 } 135 136 @After restoreDeviceForOneTime()137 public void restoreDeviceForOneTime() { 138 runWithShellPermissionIdentity( 139 () -> { 140 DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis", 141 mOldOneTimePermissionTimeoutValue, false); 142 DeviceConfig.setProperty("permissions", 143 "one_time_permissions_killed_delay_millis", 144 mOldOneTimePermissionKilledDelayValue, false); 145 }); 146 } 147 148 @Test testOneTimePermission()149 public void testOneTimePermission() throws Throwable { 150 startApp(); 151 152 CompletableFuture<Long> exitTime = registerAppExitListener(); 153 154 clickOneTimeButton(); 155 156 exitApp(); 157 158 assertGranted(5000); 159 160 assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD); 161 162 assertExpectedLifespan(exitTime, ONE_TIME_TIMEOUT_MILLIS); 163 } 164 165 @Ignore 166 @Test testForegroundServiceMaintainsPermission()167 public void testForegroundServiceMaintainsPermission() throws Throwable { 168 startApp(); 169 170 CompletableFuture<Long> exitTime = registerAppExitListener(); 171 172 clickOneTimeButton(); 173 174 long expectedLifespanMillis = 2 * ONE_TIME_TIMEOUT_MILLIS; 175 startAppForegroundService(expectedLifespanMillis, false); 176 177 exitApp(); 178 179 assertGranted(5000); 180 181 assertDenied(expectedLifespanMillis + ONE_TIME_TIMER_UPPER_GRACE_PERIOD); 182 183 assertExpectedLifespan(exitTime, expectedLifespanMillis); 184 185 } 186 187 @Test testPermissionRevokedOnKill()188 public void testPermissionRevokedOnKill() throws Throwable { 189 startApp(); 190 191 clickOneTimeButton(); 192 193 exitApp(); 194 195 assertGranted(5000); 196 197 mUiDevice.waitForIdle(); 198 SystemUtil.runWithShellPermissionIdentity(() -> 199 mActivityManager.killBackgroundProcesses(APP_PKG_NAME)); 200 201 runWithShellPermissionIdentity( 202 () -> Thread.sleep(DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, 203 "one_time_permissions_killed_delay_millis", 5000L))); 204 assertDenied(500); 205 } 206 207 @Test 208 @FlakyTest testStickyServiceMaintainsPermissionOnRestart()209 public void testStickyServiceMaintainsPermissionOnRestart() throws Throwable { 210 startApp(); 211 212 clickOneTimeButton(); 213 214 startAppForegroundService(2 * ONE_TIME_TIMEOUT_MILLIS, true); 215 216 exitApp(); 217 218 assertGranted(5000); 219 mUiDevice.waitForIdle(); 220 Thread.sleep(ONE_TIME_TIMEOUT_MILLIS); 221 222 runShellCommand("am crash " + APP_PKG_NAME); 223 224 eventually(() -> runWithShellPermissionIdentity(() -> { 225 if (mActivityManager.getPackageImportance(APP_PKG_NAME) <= IMPORTANCE_CACHED) { 226 throw new AssertionError("App was never killed"); 227 } 228 })); 229 230 eventually(() -> runWithShellPermissionIdentity(() -> { 231 if (mActivityManager.getPackageImportance(APP_PKG_NAME) 232 > IMPORTANCE_FOREGROUND_SERVICE) { 233 throw new AssertionError("Foreground service never resumed"); 234 } 235 Assert.assertEquals("Service resumed without permission", 236 PackageManager.PERMISSION_GRANTED, mContext.getPackageManager() 237 .checkPermission(ACCESS_FINE_LOCATION, APP_PKG_NAME)); 238 })); 239 } 240 241 @Test 242 @AsbSecurityTest(cveBugId = 237405974L) testCustomPermissionIsGrantedOneTime()243 public void testCustomPermissionIsGrantedOneTime() throws Throwable { 244 startApp(new ComponentName(CUSTOM_CAMERA_PERM_APP_PKG_NAME, 245 CUSTOM_CAMERA_PERM_APP_PKG_NAME + ".RequestCameraPermission")); 246 247 // We're only manually granting CAMERA, but the app will later request CUSTOM and get it 248 // granted silently. This is intentional since it's in the same group but both should 249 // eventually be revoked 250 clickOneTimeButton(); 251 252 // Just waiting for the revocation 253 eventually(() -> Assert.assertEquals(PackageManager.PERMISSION_DENIED, 254 mContext.getPackageManager() 255 .checkPermission(CAMERA, CUSTOM_CAMERA_PERM_APP_PKG_NAME)), 30000); 256 257 // This checks the vulnerability 258 eventually(() -> Assert.assertEquals(PackageManager.PERMISSION_DENIED, 259 mContext.getPackageManager() 260 .checkPermission(CUSTOM_PERMISSION, CUSTOM_CAMERA_PERM_APP_PKG_NAME)), 261 30000); 262 263 } 264 assertGrantedState(String s, int permissionGranted, long timeoutMillis)265 private void assertGrantedState(String s, int permissionGranted, long timeoutMillis) { 266 eventually(() -> Assert.assertEquals(s, 267 permissionGranted, mPackageManager 268 .checkPermission(ACCESS_FINE_LOCATION, APP_PKG_NAME)), timeoutMillis); 269 } 270 assertGranted(long timeoutMillis)271 private void assertGranted(long timeoutMillis) { 272 assertGrantedState("Permission was never granted", PackageManager.PERMISSION_GRANTED, 273 timeoutMillis); 274 } 275 assertDenied(long timeoutMillis)276 private void assertDenied(long timeoutMillis) { 277 assertGrantedState("Permission was never revoked", PackageManager.PERMISSION_DENIED, 278 timeoutMillis); 279 } 280 assertExpectedLifespan(CompletableFuture<Long> exitTime, long expectedLifespan)281 private void assertExpectedLifespan(CompletableFuture<Long> exitTime, long expectedLifespan) 282 throws InterruptedException, java.util.concurrent.ExecutionException, 283 java.util.concurrent.TimeoutException { 284 long grantedLength = System.currentTimeMillis() - exitTime.get(0, TimeUnit.MILLISECONDS); 285 if (grantedLength + ONE_TIME_TIMER_LOWER_GRACE_PERIOD < expectedLifespan) { 286 throw new AssertionError( 287 "The one time permission lived shorter than expected. expected: " 288 + expectedLifespan + "ms but was: " + grantedLength + "ms"); 289 } 290 } 291 exitApp()292 private void exitApp() { 293 eventually(() -> { 294 mUiDevice.pressBack(); 295 runWithShellPermissionIdentity(() -> { 296 DreamManager mDreamManager = mContext.getSystemService(DreamManager.class); 297 if (mDreamManager.isDreaming()) { 298 mDreamManager.stopDream(); 299 } 300 Assert.assertFalse("Unable to exit application", 301 mActivityManager.getPackageImportance(APP_PKG_NAME) 302 <= IMPORTANCE_FOREGROUND); 303 }); 304 }); 305 } 306 clickOneTimeButton()307 private void clickOneTimeButton() throws Throwable { 308 final UiObject2 uiObject = UiAutomatorUtils2.waitFindObject(By.res( 309 "com.android.permissioncontroller:id/permission_allow_one_time_button"), 10000); 310 Thread.sleep(500); 311 uiObject.click(); 312 } 313 314 /** 315 * Start the app. The app will request the permissions. 316 */ startApp(ComponentName componentName)317 private void startApp(ComponentName componentName) { 318 // One time permission is not applicable for Wear OS. 319 // The only permissions available are Allow or Deny 320 assumeFalse( 321 "Skipping test: One time permission is not supported in Wear OS", 322 FeatureUtil.isWatch()); 323 Intent startApp = new Intent(); 324 startApp.setComponent(componentName); 325 startApp.setFlags(FLAG_ACTIVITY_NEW_TASK); 326 327 mContext.startActivity(startApp); 328 } 329 330 /** 331 * Start the default app for these tests. The app will request the permissions. 332 */ startApp()333 private void startApp() { 334 startApp(new ComponentName(APP_PKG_NAME, APP_PKG_NAME + ".RequestPermission")); 335 } 336 startAppForegroundService(long lifespanMillis, boolean sticky)337 private void startAppForegroundService(long lifespanMillis, boolean sticky) { 338 Intent intent = new Intent() 339 .setComponent(new ComponentName( 340 APP_PKG_NAME, APP_PKG_NAME + ".KeepAliveForegroundService")) 341 .putExtra(EXTRA_FOREGROUND_SERVICE_LIFESPAN, lifespanMillis) 342 .putExtra(EXTRA_FOREGROUND_SERVICE_STICKY, sticky); 343 mContext.startService(intent); 344 } 345 registerAppExitListener()346 private CompletableFuture<Long> registerAppExitListener() { 347 CompletableFuture<Long> exitTimeCallback = new CompletableFuture<>(); 348 try { 349 int uid = mContext.getPackageManager().getPackageUid(APP_PKG_NAME, 0); 350 runWithShellPermissionIdentity(() -> 351 mActivityManager.addOnUidImportanceListener(new SingleAppExitListener( 352 uid, IMPORTANCE_FOREGROUND, exitTimeCallback), IMPORTANCE_FOREGROUND)); 353 } catch (PackageManager.NameNotFoundException e) { 354 throw new AssertionError("Package not found.", e); 355 } 356 return exitTimeCallback; 357 } 358 359 private class SingleAppExitListener implements ActivityManager.OnUidImportanceListener { 360 361 private final int mUid; 362 private final int mImportance; 363 private final CompletableFuture<Long> mCallback; 364 SingleAppExitListener(int uid, int importance, CompletableFuture<Long> callback)365 SingleAppExitListener(int uid, int importance, CompletableFuture<Long> callback) { 366 mUid = uid; 367 mImportance = importance; 368 mCallback = callback; 369 } 370 371 @Override onUidImportance(int uid, int importance)372 public void onUidImportance(int uid, int importance) { 373 if (uid == mUid && importance > mImportance) { 374 mCallback.complete(System.currentTimeMillis()); 375 mActivityManager.removeOnUidImportanceListener(this); 376 } 377 } 378 } 379 } 380