• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 com.android.server.om
18 
19 import android.content.om.OverlayInfo
20 import android.content.om.OverlayableInfo
21 import android.os.Process
22 import android.util.ArrayMap
23 import com.android.server.om.OverlayActorEnforcer.ActorState
24 import com.android.server.pm.pkg.AndroidPackage
25 import com.android.server.pm.pkg.PackageState
26 import com.android.server.testutils.mockThrowOnUnmocked
27 import com.android.server.testutils.whenever
28 import com.google.common.truth.Truth.assertThat
29 import org.junit.BeforeClass
30 import org.junit.Test
31 import org.junit.runner.RunWith
32 import org.junit.runners.Parameterized
33 import org.mockito.Mockito.spy
34 import java.io.IOException
35 
36 @RunWith(Parameterized::class)
37 class OverlayActorEnforcerTests {
38 
39     companion object {
40         private const val TARGET_PKG = "com.test.target"
41         private const val OVERLAY_PKG = "com.test.overlay"
42 
43         private const val VALID_NAMESPACE = "testNamespaceValid"
44         private const val INVALID_NAMESPACE = "testNamespaceInvalid"
45         private const val VALID_ACTOR_NAME = "testActorOne"
46         private const val INVALID_ACTOR_NAME = "testActorTwo"
47         private const val VALID_ACTOR_PKG = "com.test.actor.valid"
48         private const val INVALID_ACTOR_PKG = "com.test.actor.invalid"
49         private const val OVERLAYABLE_NAME = "TestOverlayable"
50         private const val NULL_UID = 3536
51         private const val EMPTY_UID = NULL_UID + 1
52         private const val INVALID_ACTOR_UID = NULL_UID + 2
53         private const val VALID_ACTOR_UID = NULL_UID + 3
54         private const val TARGET_UID = NULL_UID + 4
55         private const val USER_ID = 55
56 
57         @JvmStatic
58         @Parameterized.Parameters(name = "{0}")
59         fun parameters() = CASES.mapIndexed { caseIndex, testCase ->
60             fun param(pair: Pair<String, TestState.() -> Unit>, type: Params.Type): Params {
61                 val expectedState = testCase.state.takeUnless { type == Params.Type.ALLOWED }
62                         ?: ActorState.ALLOWED
63                 val (caseName, case) = pair
64                 val testName = makeTestName(testCase, caseName, type)
65                 return Params(caseIndex, expectedState, testName, type, case)
66             }
67 
68             testCase.failures.map { param(it, Params.Type.FAILURE) } +
69                     testCase.allowed.map { param(it, Params.Type.ALLOWED) }
70         }.flatten()
71 
72         @BeforeClass
73         @JvmStatic
74         fun checkAllCasesHandled() {
75             // Assert that all states have been tested at least once.
76             assertThat(CASES.map { it.state }.distinct())
77                     .containsAtLeastElementsIn(ActorState.values())
78         }
79 
80         @BeforeClass
81         @JvmStatic
82         fun checkAllCasesUniquelyNamed() {
83             val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase ->
84                 testCase.failures.map {
85                     makeTestName(testCase, it.first, Params.Type.FAILURE)
86                 } + testCase.allowed.map {
87                     makeTestName(testCase, it.first, Params.Type.ALLOWED)
88                 }
89             }
90                     .flatten()
91                     .groupingBy { it }
92                     .eachCount()
93                     .filterValues { it > 1 }
94                     .keys
95 
96             assertThat(duplicateCaseNames).isEmpty()
97         }
98 
99         /*
100             The pattern in this block is a result of the incredible number of branches in
101             enforcement logic. It serves to verify failures with the assumption that all errors
102             are checked in order. The idea is to emulate the if-else branches from code, but using
103             actual test data instead of if statements.
104 
105             Each state is verified by providing a failure or exclusive set of failures which cause
106             a failure state to be returned. Each state also provides a success case which will
107             "skip" the state. This allows subsequent failure cases to cascade from the first case
108             by calling all the skip branches for preceding states and then choosing only 1 of
109             the failures to test.
110 
111             Given the failure states A, B, and C: testA calls A.failure + assert, testB calls
112             A.skip + B.failure + assert, testC calls A.skip + B.skip + C.failure + assert, etc.
113 
114             Calling `allowed` is a special case for when there is a combination of parameters that
115             skips the remaining checks and immediately allows the actor through. For these cases,
116             the first failure branch will be run, assert that it's not allowed, and the
117             allowed branch will run, asserting that it now results in ALLOWED, skipping all
118             remaining functions.
119 
120             This is an ordered list of TestCase objects, with the possibility to repeat failure
121             states if any can occur multiple times in the logic tree.
122 
123             Each failure must be handled at least once.
124          */
125         private val CASES = listOf(
126                 ActorState.TARGET_NOT_FOUND withCases {
127                     failure("nullPkgInfo") { targetPkgState = null }
128                     allowed("debuggable") {
129                         targetPkgState = packageState(TARGET_PKG, debuggable = true)
130                     }
131                     skip { targetPkgState = packageState(TARGET_PKG) }
132                 },
133                 ActorState.NO_PACKAGES_FOR_UID withCases {
134                     failure("empty") { callingUid = EMPTY_UID }
135                     failure("null") { callingUid = NULL_UID }
136                     failure("shell") { callingUid = Process.SHELL_UID }
137                     allowed("targetUid") { callingUid = TARGET_UID }
138                     allowed("rootUid") { callingUid = Process.ROOT_UID }
139                     allowed("systemUid") { callingUid = Process.SYSTEM_UID }
140                     skip { callingUid = INVALID_ACTOR_UID }
141                 },
142                 ActorState.MISSING_TARGET_OVERLAYABLE_NAME withCases {
143                     failure("nullTargetOverlayableName") {
144                         overlayInfoParams.targetOverlayableName = null
145                         targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME,
146                                 "overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME")
147                     }
148                     skip { overlayInfoParams.targetOverlayableName = OVERLAYABLE_NAME }
149                 },
150                 ActorState.MISSING_LEGACY_PERMISSION withCases {
151                     failure("noPermission") {
152                         overlayInfoParams.targetOverlayableName = null
153                         targetOverlayableInfo = null
154                         hasPermission = false
155                     }
156                     allowed("hasPermission") { hasPermission = true }
157                     skip { overlayInfoParams.targetOverlayableName = OVERLAYABLE_NAME }
158                 },
159                 ActorState.ERROR_READING_OVERLAYABLE withCases {
160                     failure("doesTargetDefineOverlayableIOException") {
161                         overlayInfoParams.targetOverlayableName = null
162                         whenever(doesTargetDefineOverlayable(TARGET_PKG, USER_ID))
163                                 .thenThrow(IOException::class.java)
164                     }
165                     skip { overlayInfoParams.targetOverlayableName = OVERLAYABLE_NAME }
166                 },
167                 ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE withCases {
168                     failure("getOverlayableForTargetIOException") {
169                         whenever(getOverlayableForTarget(TARGET_PKG, OVERLAYABLE_NAME,
170                                 USER_ID)).thenThrow(IOException::class.java)
171                     }
172                 },
173                 ActorState.MISSING_OVERLAYABLE withCases {
174                     failure("nullTargetOverlayableInfo") { targetOverlayableInfo = null }
175                     skip {
176                         targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME,
177                                 "overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME")
178                     }
179                 },
180                 ActorState.MISSING_LEGACY_PERMISSION withCases {
181                     failure("noPermissionNullActor") {
182                         targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null)
183                         hasPermission = false
184                     }
185                     failure("noPermissionEmptyActor") {
186                         targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, "")
187                         hasPermission = false
188                     }
189                     allowed("hasPermissionNullActor") {
190                         hasPermission = true
191                     }
192                     skip {
193                         targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME,
194                                 "overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME")
195                     }
196                 },
197                 ActorState.INVALID_OVERLAYABLE_ACTOR_NAME withCases {
198                     fun TestState.mockActor(actorUri: String) {
199                         targetOverlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, actorUri)
200                     }
201                     failure("wrongScheme") {
202                         mockActor("notoverlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME")
203                     }
204                     failure("extraPath") {
205                         mockActor("overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME/extraPath")
206                     }
207                     failure("missingPath") { mockActor("overlay://$VALID_NAMESPACE") }
208                     failure("missingAuthority") { mockActor("overlay://") }
209                     skip { mockActor("overlay://$VALID_NAMESPACE/$VALID_ACTOR_NAME") }
210                 },
211                 ActorState.NO_NAMED_ACTORS withCases {
212                     failure("empty") { namedActorsMap = emptyMap() }
213                     skip {
214                         namedActorsMap = mapOf(INVALID_NAMESPACE to
215                                 mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG))
216                     }
217                 },
218                 ActorState.MISSING_NAMESPACE withCases {
219                     failure("invalidNamespace") {
220                         namedActorsMap = mapOf(INVALID_NAMESPACE to
221                                 mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG))
222                     }
223                     skip {
224                         namedActorsMap = mapOf(VALID_NAMESPACE to
225                                 mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG))
226                     }
227                 },
228                 ActorState.MISSING_ACTOR_NAME withCases {
229                     failure("invalidActorName") {
230                         namedActorsMap = mapOf(VALID_NAMESPACE to
231                                 mapOf(INVALID_ACTOR_NAME to VALID_ACTOR_PKG))
232                     }
233                     skip {
234                         namedActorsMap = mapOf(VALID_NAMESPACE to
235                                 mapOf(VALID_ACTOR_NAME to VALID_ACTOR_PKG))
236                     }
237                 },
238                 ActorState.ACTOR_NOT_FOUND withCases {
239                     failure("nullActorPkgInfo") { actorPkgState = null }
240                     failure("nullActorAppInfo") {
241                         actorPkgState = null
242                     }
243                     skip { actorPkgState = packageState(VALID_ACTOR_PKG) }
244                 },
245                 ActorState.ACTOR_NOT_PREINSTALLED withCases {
246                     failure("notSystem") { actorPkgState = packageState(VALID_ACTOR_PKG) }
247                     skip { actorPkgState = packageState(VALID_ACTOR_PKG, isSystem = true) }
248                 },
249                 ActorState.INVALID_ACTOR withCases {
250                     failure("invalidUid") { callingUid = INVALID_ACTOR_UID }
251                     skip { callingUid = VALID_ACTOR_UID }
252                 },
253                 ActorState.ALLOWED withCases {
254                     // No point making an exception for this case in all of the test code, so
255                     // just pretend this is a failure that results in a success result code.
256                     failure("allowed") { /* Do nothing */ }
257                 }
258         )
259 
260         data class OverlayInfoParams(
261             var targetPackageName: String = TARGET_PKG,
262             var targetOverlayableName: String? = null
263         ) {
264             fun toOverlayInfo() = OverlayInfo(
265                     OVERLAY_PKG,
266                     "",
267                     targetPackageName,
268                     targetOverlayableName,
269                     null,
270                     "/path",
271                     OverlayInfo.STATE_UNKNOWN, 0,
272                     0, false, false)
273         }
274 
275         private infix fun ActorState.withCases(block: TestCase.() -> Unit) =
276                 TestCase(this).apply(block)
277 
278         private fun packageState(
279             pkgName: String,
280             debuggable: Boolean = false,
281             isSystem: Boolean = false
282         ) = mockThrowOnUnmocked<PackageState> {
283             whenever(this.packageName).thenReturn(pkgName)
284             whenever(this.isSystem).thenReturn(isSystem)
285             val androidPackage = mockThrowOnUnmocked<AndroidPackage> {
286                 whenever(this.packageName).thenReturn(pkgName)
287                 whenever(this.isDebuggable).thenReturn(debuggable)
288             }
289             whenever(this.androidPackage).thenReturn(androidPackage)
290         }
291 
292         private fun makeTestName(testCase: TestCase, caseName: String, type: Params.Type): String {
293             val resultSuffix = if (type == Params.Type.ALLOWED) "allowed" else "failed"
294             return "${testCase.state}_${resultSuffix}_$caseName"
295         }
296     }
297 
298     @Parameterized.Parameter(0)
299     lateinit var params: Params
300 
301     @Test
302     fun verify() {
303         // Apply all the skip states before the failure to be verified
304         val testState = CASES.take(params.index)
305                 .fold(TestState.create()) { testState, case ->
306                     testState.apply(case.skip)
307                 }
308 
309         // If testing an allowed branch, first apply a failure to ensure it fails
310         if (params.type == Params.Type.ALLOWED) {
311             CASES[params.index].failures.firstOrNull()?.second?.run(testState::apply)
312             assertThat(testState.toResult()).isNotEqualTo(ActorState.ALLOWED)
313         }
314 
315         // Apply the test case in the params to the collected state
316         testState.apply(params.function)
317 
318         // Assert the result matches the expected state
319         assertThat(testState.toResult()).isEqualTo(params.expectedState)
320     }
321 
322     private fun TestState.toResult() = OverlayActorEnforcer(this)
323             .isAllowedActor("test", overlayInfoParams.toOverlayInfo(), callingUid, USER_ID)
324 
325     data class Params(
326         var index: Int,
327         var expectedState: ActorState,
328         val testName: String,
329         val type: Type,
330         val function: TestState.() -> Unit
331     ) {
332         override fun toString() = testName
333 
334         enum class Type {
335             FAILURE,
336             ALLOWED
337         }
338     }
339 
340     data class TestCase(
341         val state: ActorState,
342         val failures: MutableList<Pair<String, TestState.() -> Unit>> = mutableListOf(),
343         var allowed: MutableList<Pair<String, TestState.() -> Unit>> = mutableListOf(),
344         var skip: (TestState.() -> Unit) = {}
345     ) {
346         fun failure(caseName: String, block: TestState.() -> Unit) {
347             failures.add(caseName to block)
348         }
349 
350         fun allowed(caseName: String, block: TestState.() -> Unit) {
351             allowed.add(caseName to block)
352         }
353 
354         fun skip(block: TestState.() -> Unit) {
355             this.skip = block
356         }
357     }
358 
359     open class TestState private constructor(
360         var callingUid: Int = NULL_UID,
361         val overlayInfoParams: OverlayInfoParams = OverlayInfoParams(),
362         var namedActorsMap: Map<String, Map<String, String>> = emptyMap(),
363         var hasPermission: Boolean = false,
364         var targetOverlayableInfo: OverlayableInfo? = null,
365         var targetPkgState: PackageState? = null,
366         var actorPkgState: PackageState? = null,
367         vararg val packageNames: String = arrayOf("com.test.actor.one")
368     ) : PackageManagerHelper {
369 
370         companion object {
371             // Enforce that new instances are spied
372             fun create() = spy(TestState())!!
373         }
374 
375         override fun getNamedActors() = namedActorsMap
376 
377         override fun isInstantApp(packageName: String, userId: Int): Boolean {
378             throw UnsupportedOperationException()
379         }
380 
381         override fun initializeForUser(userId: Int): ArrayMap<String, PackageState> {
382             throw UnsupportedOperationException()
383         }
384 
385         @Throws(IOException::class)
386         override fun getOverlayableForTarget(
387             packageName: String,
388             targetOverlayableName: String,
389             userId: Int
390         ) = targetOverlayableInfo?.takeIf {
391             // Protect against this method being called with the wrong package name
392             targetPkgState == null || targetPkgState?.packageName == packageName
393         }
394 
395         override fun getPackagesForUid(uid: Int) = when (uid) {
396             EMPTY_UID -> emptyArray()
397             INVALID_ACTOR_UID -> arrayOf(INVALID_ACTOR_PKG)
398             VALID_ACTOR_UID -> arrayOf(VALID_ACTOR_PKG)
399             TARGET_UID -> arrayOf(TARGET_PKG)
400             NULL_UID -> null
401             else -> null
402         }
403 
404         @Throws(IOException::class) // Mockito requires this checked exception to be declared
405         override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean {
406             return targetOverlayableInfo?.takeIf {
407                 // Protect against this method being called with the wrong package name
408                 targetPkgState == null || targetPkgState?.packageName == targetPackageName
409             } != null
410         }
411 
412         override fun enforcePermission(permission: String?, message: String?) {
413             if (!hasPermission) {
414                 throw SecurityException()
415             }
416         }
417 
418         override fun getPackageStateForUser(packageName: String, userId: Int) =
419             listOfNotNull(targetPkgState, actorPkgState).find { it.packageName == packageName }
420 
421         override fun getConfigSignaturePackage(): String {
422             throw UnsupportedOperationException()
423         }
424 
425         override fun signaturesMatching(pkgName1: String, pkgName2: String, userId: Int): Boolean {
426             throw UnsupportedOperationException()
427         }
428     }
429 }
430