1 /*
<lambda>null2  * Copyright (C) 2020 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 android.Manifest.permission.ACCESS_MEDIA_LOCATION
20 import android.Manifest.permission.READ_EXTERNAL_STORAGE
21 import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
22 import android.app.Instrumentation
23 import android.app.UiAutomation
24 import android.content.Context
25 import android.content.pm.PackageManager
26 import android.os.Process
27 import android.os.UserHandle
28 import android.platform.test.annotations.AppModeFull
29 import androidx.test.platform.app.InstrumentationRegistry
30 import com.android.compatibility.common.util.SystemUtil
31 import org.junit.After
32 import org.junit.Assert
33 import org.junit.Assert.assertTrue
34 import org.junit.Assume.assumeNoException
35 import org.junit.Before
36 import org.junit.Test
37 
38 @AppModeFull
39 class StorageEscalationTest {
40     companion object {
41         private const val APK_DIRECTORY = "/data/local/tmp/cts/permissions"
42         const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsStorageEscalationApp28.apk"
43         const val APP_APK_PATH_29_SCOPED = "$APK_DIRECTORY/CtsStorageEscalationApp29Scoped.apk"
44         const val APP_APK_PATH_29_FULL = "$APK_DIRECTORY/CtsStorageEscalationApp29Full.apk"
45         const val APP_PACKAGE_NAME = "android.permission3.cts.storageescalation"
46         const val DELAY_TIME_MS: Long = 200
47         val permissions = listOf<String>(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
48             ACCESS_MEDIA_LOCATION)
49     }
50 
51     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
52     private val context: Context = instrumentation.context
53     private val uiAutomation: UiAutomation = instrumentation.uiAutomation
54     private var secondaryUserId: Int? = null
55 
56     @Before
57     @After
58     fun uninstallApp() {
59         SystemUtil.runShellCommand("pm uninstall $APP_PACKAGE_NAME --user ALL")
60     }
61 
62     private fun installPackage(apk: String) {
63         var userString = ""
64         secondaryUserId?.let { userId ->
65             userString = " --user $userId"
66         }
67         val result = SystemUtil.runShellCommand("pm install -r$userString $apk")
68         assertTrue("Expected output to contain \"Success\", but was \"$result\"",
69                 result.contains("Success"))
70     }
71 
72     private fun createSecondaryUser() {
73         val createUserOutput: String = SystemUtil.runShellCommand("pm create-user secondary")
74         var formatException: Exception? = null
75         val userId = try {
76             createUserOutput.split(" id ".toRegex())[1].trim { it <= ' ' }.toInt()
77         } catch (e: Exception) {
78             formatException = e
79             -1
80         }
81         assumeNoException("Failed to parse userId from $createUserOutput", formatException)
82         SystemUtil.runShellCommand("am start-user -w $userId")
83         secondaryUserId = userId
84     }
85 
86     @After
87     fun removeSecondaryUser() {
88         secondaryUserId?.let { userId ->
89             SystemUtil.runShellCommand("pm remove-user $userId")
90             secondaryUserId = null
91         }
92     }
93 
94     private fun grantStoragePermissions() {
95         for (permName in permissions) {
96             var user = Process.myUserHandle()
97             secondaryUserId?.let {
98                 user = UserHandle.of(it)
99             }
100             uiAutomation.grantRuntimePermissionAsUser(APP_PACKAGE_NAME, permName, user)
101         }
102     }
103 
104     private fun assertStoragePermissionState(granted: Boolean) {
105         for (permName in permissions) {
106             var userContext = context
107             secondaryUserId?.let { userId ->
108                 SystemUtil.runWithShellPermissionIdentity {
109                     userContext = context.createPackageContextAsUser(
110                             APP_PACKAGE_NAME, 0, UserHandle.of(userId))
111                 }
112             }
113             Assert.assertEquals(granted, userContext.packageManager.checkPermission(permName,
114                 APP_PACKAGE_NAME) == PackageManager.PERMISSION_GRANTED)
115         }
116     }
117 
118     @Test
119     fun testCannotEscalateWithSdkDowngrade() {
120         runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_28)
121     }
122 
123     @Test
124     fun testCannotEscalateWithNewManifestLegacyRequest() {
125         runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_29_FULL)
126     }
127 
128     @Test
129     fun testCannotEscalateWithSdkDowngradeSecondary() {
130         createSecondaryUser()
131         runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_28)
132     }
133 
134     @Test
135     fun testCannotEscalateWithNewManifestLegacyRequestSecondary() {
136         createSecondaryUser()
137         runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_29_FULL)
138     }
139 
140     private fun runStorageEscalationTest(startPackageApk: String, finishPackageApk: String) {
141         installPackage(startPackageApk)
142         grantStoragePermissions()
143         assertStoragePermissionState(granted = true)
144         installPackage(finishPackageApk)
145         // permission revoke is async, so wait a short period
146         Thread.sleep(DELAY_TIME_MS)
147         assertStoragePermissionState(granted = false)
148     }
149 }
150