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