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