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