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