diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt new file mode 100644 index 0000000000000..c6ccc0fb1cff0 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access + +import com.android.internal.annotations.Keep +import com.android.server.permission.access.external.PackageState + +@Keep +class AccessCheckingService { + @Volatile + private lateinit var state: AccessState + private val stateLock = Any() + + private val policy = AccessPolicy() + + private val persistence = AccessPersistence(policy) + + fun init() { + val state = AccessState() + state.systemState.userIds.apply { + // TODO: Get and add all user IDs. + // TODO: Maybe get and add all packages? + } + persistence.read(state) + this.state = state + } + + fun getDecision(subject: AccessUri, `object`: AccessUri): Int = + policy.getDecision(subject, `object`, state) + + fun setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) { + mutateState { oldState, newState -> + policy.setDecision(subject, `object`, decision, oldState, newState) + } + } + + fun onUserAdded(userId: Int) { + mutateState { oldState, newState -> + policy.onUserAdded(userId, oldState, newState) + } + } + + fun onUserRemoved(userId: Int) { + mutateState { oldState, newState -> + policy.onUserRemoved(userId, oldState, newState) + } + } + + fun onPackageAdded(packageState: PackageState) { + mutateState { oldState, newState -> + policy.onPackageAdded(packageState, oldState, newState) + } + } + + fun onPackageRemoved(packageState: PackageState) { + mutateState { oldState, newState -> + policy.onPackageRemoved(packageState, oldState, newState) + } + } + + // TODO: Replace (oldState, newState) with Kotlin context receiver once it's stabilized. + private inline fun mutateState(action: (oldState: AccessState, newState: AccessState) -> Unit) { + synchronized(stateLock) { + val oldState = state + val newState = oldState.copy() + action(oldState, newState) + persistence.write(newState) + state = newState + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt new file mode 100644 index 0000000000000..0efc1bd00de29 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access + +import android.util.AtomicFile +import android.util.Log +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.util.PermissionApex +import com.android.server.permission.access.util.parseBinaryXml +import com.android.server.permission.access.util.read +import com.android.server.permission.access.util.serializeBinaryXml +import com.android.server.permission.access.util.writeInlined +import java.io.File +import java.io.FileNotFoundException + +class AccessPersistence( + private val policy: AccessPolicy +) { + fun read(state: AccessState) { + readSystemState(state.systemState) + val userStates = state.userStates + state.systemState.userIds.forEachIndexed { _, userId -> + readUserState(userId, userStates[userId]) + } + } + + private fun readSystemState(systemState: SystemState) { + systemFile.parse { + // This is the canonical way to call an extension function in a different class. + // TODO(b/259469752): Use context receiver for this when it becomes stable. + with(policy) { this@parse.parseSystemState(systemState) } + } + } + + private fun readUserState(userId: Int, userState: UserState) { + getUserFile(userId).parse { + with(policy) { this@parse.parseUserState(userId, userState) } + } + } + + private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit) { + try { + AtomicFile(this).read { it.parseBinaryXml(block) } + } catch (e: FileNotFoundException) { + Log.i(LOG_TAG, "$this not found") + } catch (e: Exception) { + throw IllegalStateException("Failed to read $this", e) + } + } + + fun write(state: AccessState) { + writeState(state.systemState, ::writeSystemState) + state.userStates.forEachIndexed { _, userId, userState -> + writeState(userState) { writeUserState(userId, it) } + } + } + + private inline fun writeState(state: T, write: (T) -> Unit) { + when (val writeMode = state.writeMode) { + WriteMode.NONE -> {} + WriteMode.SYNC -> write(state) + WriteMode.ASYNC -> TODO() + else -> error(writeMode) + } + } + + private fun writeSystemState(systemState: SystemState) { + systemFile.serialize { + with(policy) { this@serialize.serializeSystemState(systemState) } + } + } + + private fun writeUserState(userId: Int, userState: UserState) { + getUserFile(userId).serialize { + with(policy) { this@serialize.serializeUserState(userId, userState) } + } + } + + private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) { + try { + AtomicFile(this).writeInlined { it.serializeBinaryXml(block) } + } catch (e: Exception) { + Log.e(LOG_TAG, "Failed to serialize $this", e) + } + } + + private val systemFile: File + get() = File(PermissionApex.systemDataDirectory, FILE_NAME) + + private fun getUserFile(userId: Int): File = + File(PermissionApex.getUserDataDirectory(userId), FILE_NAME) + + companion object { + private val LOG_TAG = AccessPersistence::class.java.simpleName + + private const val FILE_NAME = "access.abx" + } +} diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt new file mode 100644 index 0000000000000..4e53ce06f3eb7 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access + +import android.util.Log +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.appop.PackageAppOpPolicy +import com.android.server.permission.access.appop.UidAppOpPolicy +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.external.PackageState +import com.android.server.permission.access.permission.UidPermissionPolicy +import com.android.server.permission.access.util.forEachTag +import com.android.server.permission.access.util.tag +import com.android.server.permission.access.util.tagName + +class AccessPolicy private constructor( + private val schemePolicies: IndexedMap> +) { + constructor() : this( + IndexedMap>().apply { + fun addPolicy(policy: SchemePolicy) = + getOrPut(policy.subjectScheme) { IndexedMap() }.put(policy.objectScheme, policy) + addPolicy(UidPermissionPolicy()) + addPolicy(UidAppOpPolicy()) + addPolicy(PackageAppOpPolicy()) + } + ) + + fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int = + getSchemePolicy(subject, `object`).getDecision(subject, `object`, state) + + fun setDecision( + subject: AccessUri, + `object`: AccessUri, + decision: Int, + oldState: AccessState, + newState: AccessState + ) { + getSchemePolicy(subject, `object`) + .setDecision(subject, `object`, decision, oldState, newState) + } + + private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy = + checkNotNull(schemePolicies[subject.scheme]?.get(`object`.scheme)) { + "Scheme policy for subject=$subject object=$`object` does not exist" + } + + fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) { + newState.systemState.userIds += userId + newState.userStates[userId] = UserState() + forEachSchemePolicy { it.onUserAdded(userId, oldState, newState) } + } + + fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) { + newState.systemState.userIds -= userId + newState.userStates -= userId + forEachSchemePolicy { it.onUserRemoved(userId, oldState, newState) } + } + + fun onPackageAdded(packageState: PackageState, oldState: AccessState, newState: AccessState) { + var isAppIdAdded = false + newState.systemState.apply { + packageStates[packageState.packageName] = packageState + appIds.getOrPut(packageState.appId) { + isAppIdAdded = true + IndexedListSet() + }.add(packageState.packageName) + } + if (isAppIdAdded) { + forEachSchemePolicy { it.onAppIdAdded(packageState.appId, oldState, newState) } + } + forEachSchemePolicy { it.onPackageAdded(packageState, oldState, newState) } + } + + fun onPackageRemoved(packageState: PackageState, oldState: AccessState, newState: AccessState) { + var isAppIdRemoved = false + newState.systemState.apply { + packageStates -= packageState.packageName + appIds.apply appIds@{ + this[packageState.appId]?.apply { + this -= packageState.packageName + if (isEmpty()) { + this@appIds -= packageState.appId + isAppIdRemoved = true + } + } + } + } + forEachSchemePolicy { it.onPackageRemoved(packageState, oldState, newState) } + if (isAppIdRemoved) { + forEachSchemePolicy { it.onAppIdRemoved(packageState.appId, oldState, newState) } + } + } + + fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) { + forEachTag { + when (tagName) { + TAG_ACCESS -> { + forEachTag { + forEachSchemePolicy { + with(it) { this@parseSystemState.parseSystemState(systemState) } + } + } + } + else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state") + } + } + } + + fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) { + tag(TAG_ACCESS) { + forEachSchemePolicy { + with(it) { this@serializeSystemState.serializeSystemState(systemState) } + } + } + } + + fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) { + forEachTag { + when (tagName) { + TAG_ACCESS -> { + forEachTag { + forEachSchemePolicy { + with(it) { this@parseUserState.parseUserState(userId, userState) } + } + } + } + else -> { + Log.w( + LOG_TAG, + "Ignoring unknown tag $tagName when parsing user state for user $userId" + ) + } + } + } + } + + fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) { + tag(TAG_ACCESS) { + forEachSchemePolicy { + with(it) { this@serializeUserState.serializeUserState(userId, userState) } + } + } + } + + private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) { + schemePolicies.forEachValueIndexed { _, objectSchemePolicies -> + objectSchemePolicies.forEachValueIndexed { _, schemePolicy -> + action(schemePolicy) + } + } + } + + companion object { + private val LOG_TAG = AccessPolicy::class.java.simpleName + + private const val TAG_ACCESS = "access" + } +} + +abstract class SchemePolicy { + @Volatile + private var onDecisionChangedListeners = IndexedListSet() + private val onDecisionChangedListenersLock = Any() + + abstract val subjectScheme: String + + abstract val objectScheme: String + + abstract fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int + + abstract fun setDecision( + subject: AccessUri, + `object`: AccessUri, + decision: Int, + oldState: AccessState, + newState: AccessState + ) + + fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) { + synchronized(onDecisionChangedListenersLock) { + onDecisionChangedListeners = onDecisionChangedListeners + listener + } + } + + fun removeOnDecisionChangedListener(listener: OnDecisionChangedListener) { + synchronized(onDecisionChangedListenersLock) { + onDecisionChangedListeners = onDecisionChangedListeners - listener + } + } + + protected fun notifyOnDecisionChangedListeners( + subject: AccessUri, + `object`: AccessUri, + oldDecision: Int, + newDecision: Int + ) { + val listeners = onDecisionChangedListeners + listeners.forEachIndexed { _, it -> + it.onDecisionChanged(subject, `object`, oldDecision, newDecision) + } + } + + open fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {} + + open fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {} + + open fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {} + + open fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {} + + open fun onPackageAdded( + packageState: PackageState, + oldState: AccessState, + newState: AccessState + ) {} + + open fun onPackageRemoved( + packageState: PackageState, + oldState: AccessState, + newState: AccessState + ) {} + + open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {} + + open fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {} + + open fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {} + + open fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {} + + fun interface OnDecisionChangedListener { + fun onDecisionChanged( + subject: AccessUri, + `object`: AccessUri, + oldDecision: Int, + newDecision: Int + ) + } +} diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt new file mode 100644 index 0000000000000..f496dbd03130e --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access + +import android.content.pm.PermissionGroupInfo +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.data.Permission +import com.android.server.permission.access.external.PackageState + +class AccessState private constructor( + val systemState: SystemState, + val userStates: IntMap +) { + constructor() : this(SystemState(), IntMap()) + + fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() }) +} + +class SystemState private constructor( + val userIds: IntSet, + val packageStates: IndexedMap, + val disabledSystemPackageStates: IndexedMap, + val appIds: IntMap>, + // A map of KnownPackagesInt to a set of known package names + val knownPackages: IntMap>, + // A map of userId to packageName + val deviceAndProfileOwners: IntMap, + // A map of packageName to (A map of oem permission name to whether it's granted) + val oemPermissions: IndexedMap>, + val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet, + // A map of packageName to a set of vendor priv app permission names + val vendorPrivAppPermissions: Map>, + val productPrivAppPermissions: Map>, + val systemExtPrivAppPermissions: Map>, + val privAppPermissions: Map>, + val apexPrivAppPermissions: Map>>, + val vendorPrivAppDenyPermissions: Map>, + val productPrivAppDenyPermissions: Map>, + val systemExtPrivAppDenyPermissions: Map>, + val apexPrivAppDenyPermissions: Map>>, + val privAppDenyPermissions: Map>, + val implicitToSourcePermissions: Map>, + val permissionGroups: IndexedMap, + val permissionTrees: IndexedMap, + val permissions: IndexedMap +) : WritableState() { + constructor() : this( + IntSet(), IndexedMap(), IndexedMap(), IntMap(), IntMap(), IntMap(), IndexedMap(), + IndexedListSet(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), + IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), + IndexedMap(), IndexedMap(), IndexedMap() + ) + + fun copy(): SystemState = + SystemState( + userIds.copy(), + packageStates.copy { it }, + disabledSystemPackageStates.copy { it }, + appIds.copy { it.copy() }, + knownPackages.copy { it.copy() }, + deviceAndProfileOwners.copy { it }, + oemPermissions.copy { it.copy { it } }, + privilegedPermissionAllowlistSourcePackageNames.copy(), + vendorPrivAppPermissions, + productPrivAppPermissions, + systemExtPrivAppPermissions, + privAppPermissions, + apexPrivAppPermissions, + vendorPrivAppDenyPermissions, + productPrivAppDenyPermissions, + systemExtPrivAppDenyPermissions, + apexPrivAppDenyPermissions, + privAppDenyPermissions, + implicitToSourcePermissions, + permissionGroups.copy { it }, + permissionTrees.copy { it }, + permissions.copy { it } + ) +} + +class UserState private constructor( + // A map of (appId to a map of (permissionName to permissionFlags)) + val permissionFlags: IntMap>, + val uidAppOpModes: IntMap>, + val packageAppOpModes: IndexedMap> +) : WritableState() { + constructor() : this(IntMap(), IntMap(), IndexedMap()) + + fun copy(): UserState = UserState(permissionFlags.copy { it.copy { it } }, + uidAppOpModes.copy { it.copy { it } }, packageAppOpModes.copy { it.copy { it } }) +} + +object WriteMode { + const val NONE = 0 + const val SYNC = 1 + const val ASYNC = 2 +} + +abstract class WritableState { + var writeMode: Int = WriteMode.NONE + private set + + fun requestWrite(sync: Boolean = false) { + if (sync) { + writeMode = WriteMode.SYNC + } else { + if (writeMode != WriteMode.SYNC) { + writeMode = WriteMode.ASYNC + } + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt new file mode 100644 index 0000000000000..7e98d2cb13e76 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access + +import com.android.server.permission.access.external.UserHandle +import com.android.server.permission.access.external.UserHandleCompat + +sealed class AccessUri( + val scheme: String +) { + override fun equals(other: Any?): Boolean { + throw NotImplementedError() + } + + override fun hashCode(): Int { + throw NotImplementedError() + } + + override fun toString(): String { + throw NotImplementedError() + } +} + +data class AppOpUri( + val appOpName: String +) : AccessUri(SCHEME) { + override fun toString(): String = "$scheme:///$appOpName" + + companion object { + const val SCHEME = "app-op" + } +} + +data class PackageUri( + val packageName: String, + val userId: Int +) : AccessUri(SCHEME) { + override fun toString(): String = "$scheme:///$packageName/$userId" + + companion object { + const val SCHEME = "package" + } +} + +data class PermissionUri( + val permissionName: String +) : AccessUri(SCHEME) { + override fun toString(): String = "$scheme:///$permissionName" + + companion object { + const val SCHEME = "permission" + } +} + +data class UidUri( + val uid: Int +) : AccessUri(SCHEME) { + val userId: Int + get() = UserHandleCompat.getUserId(uid) + + val appId: Int + get() = UserHandle.getAppId(uid) + + override fun toString(): String = "$scheme:///$uid" + + companion object { + const val SCHEME = "uid" + } +} diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpModes.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpModes.kt new file mode 100644 index 0000000000000..d90846ffad473 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpModes.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.appop + +import android.app.AppOpsManager + +object AppOpModes { + const val MODE_ALLOWED = AppOpsManager.MODE_ALLOWED + const val MODE_IGNORED = AppOpsManager.MODE_IGNORED + const val MODE_ERRORED = AppOpsManager.MODE_ERRORED + const val MODE_DEFAULT = AppOpsManager.MODE_DEFAULT + const val MODE_FOREGROUND = AppOpsManager.MODE_FOREGROUND +} diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt new file mode 100644 index 0000000000000..031c9287a9a46 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.appop + +import android.util.Log +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.UserState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.util.attributeInt +import com.android.server.permission.access.util.attributeInterned +import com.android.server.permission.access.util.forEachTag +import com.android.server.permission.access.util.getAttributeIntOrThrow +import com.android.server.permission.access.util.getAttributeValueOrThrow +import com.android.server.permission.access.util.tag +import com.android.server.permission.access.util.tagName + +abstract class BaseAppOpPersistence { + abstract fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) + + abstract fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) + + protected fun BinaryXmlPullParser.parseAppOps(appOpModes: IndexedMap) { + forEachTag { + when (tagName) { + TAG_APP_OP -> parseAppOp(appOpModes) + else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") + } + } + } + + private fun BinaryXmlPullParser.parseAppOp(appOpModes: IndexedMap) { + val name = getAttributeValueOrThrow(ATTR_NAME).intern() + val mode = getAttributeIntOrThrow(ATTR_MODE) + appOpModes[name] = mode + } + + protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap) { + appOpModes.forEachIndexed { _, name, mode -> + serializeAppOp(name, mode) + } + } + + private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) { + tag(TAG_APP_OP) { + attributeInterned(ATTR_NAME, name) + attributeInt(ATTR_MODE, mode) + } + } + + companion object { + private val LOG_TAG = BaseAppOpPersistence::class.java.simpleName + + private const val TAG_APP_OP = "app-op" + + private const val ATTR_NAME = "name" + private const val ATTR_MODE = "mode" + } +} diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt new file mode 100644 index 0000000000000..0b052f918a20d --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.appop + +import android.app.AppOpsManager +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.AccessState +import com.android.server.permission.access.AccessUri +import com.android.server.permission.access.AppOpUri +import com.android.server.permission.access.SchemePolicy +import com.android.server.permission.access.UserState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports + +abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() { + override fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int { + `object` as AppOpUri + return getModes(subject, state) + .getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName)) + } + + override fun setDecision( + subject: AccessUri, + `object`: AccessUri, + decision: Int, + oldState: AccessState, + newState: AccessState + ) { + `object` as AppOpUri + val modes = getOrCreateModes(subject, newState) + val oldMode = modes.putWithDefault(`object`.appOpName, decision, + opToDefaultMode(`object`.appOpName)) + if (modes.isEmpty()) { + removeModes(subject, newState) + } + if (oldMode != decision) { + notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision) + } + } + + abstract fun getModes(subject: AccessUri, state: AccessState): IndexedMap? + + abstract fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap + + abstract fun removeModes(subject: AccessUri, state: AccessState) + + // TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue + // since running in system process. + private fun opToDefaultMode(appOpName: String) = AppOpsManager.opToDefaultMode(appOpName) + + override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) { + with(persistence) { this@parseUserState.parseUserState(userId, userState) } + } + + override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) { + with(persistence) { this@serializeUserState.serializeUserState(userId, userState) } + } +} diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt new file mode 100644 index 0000000000000..183a352b9acd0 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPersistence.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.appop + +import android.util.Log +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.UserState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.util.attributeInterned +import com.android.server.permission.access.util.forEachTag +import com.android.server.permission.access.util.getAttributeValueOrThrow +import com.android.server.permission.access.util.tag +import com.android.server.permission.access.util.tagName + +class PackageAppOpPersistence : BaseAppOpPersistence() { + override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) { + when (tagName) { + TAG_PACKAGE_APP_OPS -> parsePackageAppOps(userState) + else -> {} + } + } + + private fun BinaryXmlPullParser.parsePackageAppOps(userState: UserState) { + forEachTag { + when (tagName) { + TAG_PACKAGE -> parsePackage(userState) + else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") + } + } + } + + private fun BinaryXmlPullParser.parsePackage(userState: UserState) { + val packageName = getAttributeValueOrThrow(ATTR_NAME).intern() + val appOpModes = IndexedMap() + userState.packageAppOpModes[packageName] = appOpModes + parseAppOps(appOpModes) + } + + override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) { + serializePackageAppOps(userState) + } + + private fun BinaryXmlSerializer.serializePackageAppOps(userState: UserState) { + tag(TAG_PACKAGE_APP_OPS) { + userState.packageAppOpModes.forEachIndexed { _, packageName, appOpModes -> + serializePackage(packageName, appOpModes) + } + } + } + + private fun BinaryXmlSerializer.serializePackage( + packageName: String, + appOpModes: IndexedMap + ) { + tag(TAG_PACKAGE) { + attributeInterned(ATTR_NAME, packageName) + serializeAppOps(appOpModes) + } + } + + companion object { + private val LOG_TAG = PackageAppOpPersistence::class.java.simpleName + + private const val TAG_PACKAGE_APP_OPS = "package-app-ops" + private const val TAG_PACKAGE = "package" + + private const val ATTR_NAME = "name" + } +} diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt new file mode 100644 index 0000000000000..7c3c14c1d753e --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.appop + +import com.android.server.permission.access.AccessState +import com.android.server.permission.access.AccessUri +import com.android.server.permission.access.AppOpUri +import com.android.server.permission.access.PackageUri +import com.android.server.permission.access.UserState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.external.PackageState + +class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { + override val subjectScheme: String + get() = PackageUri.SCHEME + + override val objectScheme: String + get() = AppOpUri.SCHEME + + override fun getModes(subject: AccessUri, state: AccessState): IndexedMap? { + subject as PackageUri + return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName) + } + + override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap { + subject as PackageUri + return state.userStates.getOrPut(subject.userId) { UserState() } + .packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() } + } + + override fun removeModes(subject: AccessUri, state: AccessState) { + subject as PackageUri + state.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName) + } + + override fun onPackageRemoved( + packageState: PackageState, + oldState: AccessState, + newState: AccessState + ) { + newState.userStates.forEachIndexed { _, _, userState -> + userState.packageAppOpModes -= packageState.packageName + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt new file mode 100644 index 0000000000000..3c3a9d18926a3 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPersistence.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.appop + +import android.util.Log +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.UserState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.util.attributeInt +import com.android.server.permission.access.util.forEachTag +import com.android.server.permission.access.util.getAttributeIntOrThrow +import com.android.server.permission.access.util.tag +import com.android.server.permission.access.util.tagName + +class UidAppOpPersistence : BaseAppOpPersistence() { + override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) { + when (tagName) { + TAG_UID_APP_OPS -> parseUidAppOps(userState) + else -> {} + } + } + + private fun BinaryXmlPullParser.parseUidAppOps(userState: UserState) { + forEachTag { + when (tagName) { + TAG_UID -> parseUid(userState) + else -> Log.w(LOG_TAG, "Ignoring unknown tag $name when parsing app-op state") + } + } + } + + private fun BinaryXmlPullParser.parseUid(userState: UserState) { + val uid = getAttributeIntOrThrow(ATTR_UID) + val appOpModes = IndexedMap() + userState.uidAppOpModes[uid] = appOpModes + parseAppOps(appOpModes) + } + + override fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) { + serializeUidAppOps(userState) + } + + private fun BinaryXmlSerializer.serializeUidAppOps(userState: UserState) { + tag(TAG_UID_APP_OPS) { + userState.uidAppOpModes.forEachIndexed { _, uid, appOpModes -> + serializeUid(uid, appOpModes) + } + } + } + + private fun BinaryXmlSerializer.serializeUid(uid: Int, appOpModes: IndexedMap) { + tag(TAG_UID) { + attributeInt(ATTR_UID, uid) + serializeAppOps(appOpModes) + } + } + + companion object { + private val LOG_TAG = UidAppOpPersistence::class.java.simpleName + + private const val TAG_UID_APP_OPS = "uid-app-ops" + private const val TAG_UID = "uid" + + private const val ATTR_UID = "uid" + } +} diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt new file mode 100644 index 0000000000000..26d01144609af --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.appop + +import com.android.server.permission.access.AccessState +import com.android.server.permission.access.AccessUri +import com.android.server.permission.access.AppOpUri +import com.android.server.permission.access.UidUri +import com.android.server.permission.access.UserState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports + +class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) { + override val subjectScheme: String + get() = UidUri.SCHEME + + override val objectScheme: String + get() = AppOpUri.SCHEME + + override fun getModes(subject: AccessUri, state: AccessState): IndexedMap? { + subject as UidUri + return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId) + } + + override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap { + subject as UidUri + return state.userStates.getOrPut(subject.userId) { UserState() } + .uidAppOpModes.getOrPut(subject.appId) { IndexedMap() } + } + + override fun removeModes(subject: AccessUri, state: AccessState) { + subject as UidUri + state.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId) + } + + override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) { + newState.userStates.forEachIndexed { _, _, userState -> + userState.uidAppOpModes -= appId + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt new file mode 100644 index 0000000000000..5ba435c79e2f1 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +typealias IndexedList = ArrayList + +inline fun IndexedList.allIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (!predicate(index, this[index])) { + return false + } + } + return true +} + +inline fun IndexedList.anyIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, this[index])) { + return true + } + } + return false +} + +@Suppress("NOTHING_TO_INLINE") +inline fun IndexedList.copy(): IndexedList = IndexedList(this) + +inline fun IndexedList.forEachIndexed(action: (Int, T) -> Unit) { + for (index in indices) { + action(index, this[index]) + } +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedList.minus(element: T): IndexedList = + copy().apply { this -= element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedList.minusAssign(element: T) { + remove(element) +} + +inline fun IndexedList.noneIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, this[index])) { + return false + } + } + return true +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedList.plus(element: T): IndexedList = + copy().apply { this += element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedList.plusAssign(element: T) { + add(element) +} + +inline fun IndexedList.removeAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (predicate(index, this[index])) { + removeAt(index) + } + } +} + +inline fun IndexedList.retainAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (!predicate(index, this[index])) { + removeAt(index) + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt new file mode 100644 index 0000000000000..ac552fff6cdb6 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +class IndexedListSet private constructor( + private val list: ArrayList +) : MutableSet { + constructor() : this(ArrayList()) + + override val size: Int + get() = list.size + + override fun contains(element: T): Boolean = list.contains(element) + + override fun isEmpty(): Boolean = list.isEmpty() + + override fun iterator(): MutableIterator = list.iterator() + + override fun containsAll(elements: Collection): Boolean { + throw NotImplementedError() + } + + fun elementAt(index: Int): T = list[index] + + fun indexOf(element: T): Int = list.indexOf(element) + + override fun add(element: T): Boolean = + if (list.contains(element)) { + false + } else { + list.add(element) + true + } + + override fun remove(element: T): Boolean = list.remove(element) + + override fun clear() { + list.clear() + } + + override fun addAll(elements: Collection): Boolean { + throw NotImplementedError() + } + + override fun removeAll(elements: Collection): Boolean { + throw NotImplementedError() + } + + override fun retainAll(elements: Collection): Boolean { + throw NotImplementedError() + } + + fun removeAt(index: Int): T? = list.removeAt(index) + + fun copy(): IndexedListSet = IndexedListSet(ArrayList(list)) +} + +inline fun IndexedListSet.allIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (!predicate(index, elementAt(index))) { + return false + } + } + return true +} + +inline fun IndexedListSet.anyIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, elementAt(index))) { + return true + } + } + return false +} + +inline fun IndexedListSet.forEachIndexed(action: (Int, T) -> Unit) { + for (index in indices) { + action(index, elementAt(index)) + } +} + +inline val IndexedListSet.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedListSet.minus(element: T): IndexedListSet = + copy().apply { this -= element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedListSet.minusAssign(element: T) { + remove(element) +} + +inline fun IndexedListSet.noneIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, elementAt(index))) { + return false + } + } + return true +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedListSet.plus(element: T): IndexedListSet = + copy().apply { this += element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedListSet.plusAssign(element: T) { + add(element) +} + +inline fun IndexedListSet.removeAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (predicate(index, elementAt(index))) { + removeAt(index) + } + } +} + +inline fun IndexedListSet.retainAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (!predicate(index, elementAt(index))) { + removeAt(index) + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt new file mode 100644 index 0000000000000..1251666089f5d --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +import android.util.ArrayMap + +typealias IndexedMap = ArrayMap + +inline fun IndexedMap.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean { + for (index in 0 until size) { + if (!predicate(index, keyAt(index), valueAt(index))) { + return false + } + } + return true +} + +inline fun IndexedMap.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, keyAt(index), valueAt(index))) { + return true + } + } + return false +} + +inline fun IndexedMap.copy(copyValue: (V) -> V): IndexedMap = + IndexedMap(this).apply { + forEachValueIndexed { index, value -> + setValueAt(index, copyValue(value)) + } + } + +inline fun IndexedMap.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? { + for (index in 0 until size) { + transform(index, keyAt(index), valueAt(index))?.let { return it } + } + return null +} + +inline fun IndexedMap.forEachIndexed(action: (Int, K, V) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun IndexedMap.forEachKeyIndexed(action: (Int, K) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index)) + } +} + +inline fun IndexedMap.forEachValueIndexed(action: (Int, V) -> Unit) { + for (index in 0 until size) { + action(index, valueAt(index)) + } +} + +inline fun IndexedMap.getOrPut(key: K, defaultValue: () -> V): V { + get(key)?.let { return it } + return defaultValue().also { put(key, it) } +} + +@Suppress("NOTHING_TO_INLINE") +inline fun IndexedMap?.getWithDefault(key: K, defaultValue: V): V { + this ?: return defaultValue + val index = indexOfKey(key) + return if (index >= 0) valueAt(index) else defaultValue +} + +inline val IndexedMap.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedMap.minusAssign(key: K) { + remove(key) +} + +@Suppress("NOTHING_TO_INLINE") +inline fun IndexedMap.putWithDefault(key: K, value: V, defaultValue: V): V { + val index = indexOfKey(key) + if (index >= 0) { + val oldValue = valueAt(index) + if (value != oldValue) { + if (value == defaultValue) { + removeAt(index) + } else { + setValueAt(index, value) + } + } + return oldValue + } else { + if (value != defaultValue) { + put(key, value) + } + return defaultValue + } +} + +inline fun IndexedMap.removeAllIndexed(predicate: (Int, K, V) -> Boolean) { + for (index in lastIndex downTo 0) { + if (predicate(index, keyAt(index), valueAt(index))) { + removeAt(index) + } + } +} + +inline fun IndexedMap.retainAllIndexed(predicate: (Int, K, V) -> Boolean) { + for (index in lastIndex downTo 0) { + if (!predicate(index, keyAt(index), valueAt(index))) { + removeAt(index) + } + } +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedMap.set(key: K, value: V) { + put(key, value) +} diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt new file mode 100644 index 0000000000000..36d8ff0c00873 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +import android.util.ArraySet + +typealias IndexedSet = ArraySet + +inline fun IndexedSet.allIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (!predicate(index, elementAt(index))) { + return false + } + } + return true +} + +inline fun IndexedSet.anyIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, elementAt(index))) { + return true + } + } + return false +} + +@Suppress("NOTHING_TO_INLINE") +inline fun IndexedSet.copy(): IndexedSet = IndexedSet(this) + +@Suppress("NOTHING_TO_INLINE") +inline fun IndexedSet.elementAt(index: Int): T = valueAt(index) + +inline fun IndexedSet.forEachIndexed(action: (Int, T) -> Unit) { + for (index in indices) { + action(index, elementAt(index)) + } +} + +inline val IndexedSet.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedSet.minus(element: T): IndexedSet = + copy().apply { this -= element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedSet.minusAssign(element: T) { + remove(element) +} + +inline fun IndexedSet.noneIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, elementAt(index))) { + return false + } + } + return true +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedSet.plus(element: T): IndexedSet = + copy().apply { this += element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IndexedSet.plusAssign(element: T) { + add(element) +} + +inline fun IndexedSet.removeAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (predicate(index, elementAt(index))) { + removeAt(index) + } + } +} + +inline fun IndexedSet.retainAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (!predicate(index, elementAt(index))) { + removeAt(index) + } + } +} + +@Suppress("NOTHING_TO_INLINE") +inline fun indexedSetOf(vararg elements: T): IndexedSet = IndexedSet(elements.asList()) diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt new file mode 100644 index 0000000000000..9051c66120d8b --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +import android.util.SparseArray + +typealias IntMap = SparseArray + +inline fun IntMap.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (!predicate(index, keyAt(index), valueAt(index))) { + return false + } + } + return true +} + +inline fun IntMap.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, keyAt(index), valueAt(index))) { + return true + } + } + return false +} + +inline fun IntMap.copy(copyValue: (T) -> T): IntMap = + this.clone().apply { + forEachValueIndexed { index, value -> + setValueAt(index, copyValue(value)) + } + } + +inline fun IntMap.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? { + for (index in 0 until size) { + transform(index, keyAt(index), valueAt(index))?.let { return it } + } + return null +} + +inline fun IntMap.forEachIndexed(action: (Int, Int, T) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun IntMap.forEachKeyIndexed(action: (Int, Int) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index)) + } +} + +inline fun IntMap.forEachValueIndexed(action: (Int, T) -> Unit) { + for (index in 0 until size) { + action(index, valueAt(index)) + } +} + +inline fun IntMap.getOrPut(key: Int, defaultValue: () -> T): T { + get(key)?.let { return it } + return defaultValue().also { put(key, it) } +} + +@Suppress("NOTHING_TO_INLINE") +inline fun IntMap?.getWithDefault(key: Int, defaultValue: T): T { + this ?: return defaultValue + val index = indexOfKey(key) + return if (index >= 0) valueAt(index) else defaultValue +} + +inline val IntMap.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IntMap.minusAssign(key: Int) { + remove(key) +} + +inline fun IntMap.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, keyAt(index), valueAt(index))) { + return false + } + } + return true +} + +@Suppress("NOTHING_TO_INLINE") +inline fun IntMap.putWithDefault(key: Int, value: T, defaultValue: T): T { + val index = indexOfKey(key) + if (index >= 0) { + val oldValue = valueAt(index) + if (value != oldValue) { + if (value == defaultValue) { + removeAt(index) + } else { + setValueAt(index, value) + } + } + return oldValue + } else { + if (value != defaultValue) { + put(key, value) + } + return defaultValue + } +} + +inline fun IntMap.removeAllIndexed(predicate: (Int, Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (predicate(index, keyAt(index), valueAt(index))) { + removeAt(index) + } + } +} + +inline fun IntMap.retainAllIndexed(predicate: (Int, Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (!predicate(index, keyAt(index), valueAt(index))) { + removeAt(index) + } + } +} + +inline val IntMap.size: Int + get() = size() diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt new file mode 100644 index 0000000000000..d6dfe9d0eb371 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +import android.util.SparseBooleanArray + +class IntSet private constructor( + private val array: SparseBooleanArray +) { + constructor() : this(SparseBooleanArray()) + + val size: Int + get() = array.size() + + operator fun contains(element: Int): Boolean = array[element] + + fun elementAt(index: Int): Int = array.keyAt(index) + + fun indexOf(element: Int): Int = array.indexOfKey(element) + + fun add(element: Int) { + array.put(element, true) + } + + fun remove(element: Int) { + array.delete(element) + } + + fun clear() { + array.clear() + } + + fun removeAt(index: Int) { + array.removeAt(index) + } + + fun copy(): IntSet = IntSet(array.clone()) +} + +inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean { + for (index in 0 until size) { + if (!predicate(index, elementAt(index))) { + return false + } + } + return true +} + +inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, elementAt(index))) { + return true + } + } + return false +} + +inline fun IntSet.forEachIndexed(action: (Int, Int) -> Unit) { + for (index in 0 until size) { + action(index, elementAt(index)) + } +} + +inline val IntSet.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IntSet.minus(element: Int): IntSet = copy().apply { this -= element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IntSet.minusAssign(element: Int) { + remove(element) +} + +inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, elementAt(index))) { + return false + } + } + return true +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IntSet.plus(element: Int): IntSet = copy().apply { this += element } + +@Suppress("NOTHING_TO_INLINE") +inline operator fun IntSet.plusAssign(element: Int) { + add(element) +} + +inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean) { + for (index in lastIndex downTo 0) { + if (predicate(index, elementAt(index))) { + removeAt(index) + } + } +} + +inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean) { + for (index in lastIndex downTo 0) { + if (!predicate(index, elementAt(index))) { + removeAt(index) + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/List.kt new file mode 100644 index 0000000000000..dc28642bc2c6b --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/List.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +inline fun List.allIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (!predicate(index, this[index])) { + return false + } + } + return true +} + +inline fun List.anyIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, this[index])) { + return true + } + } + return false +} + +inline fun List.forEachIndexed(action: (Int, T) -> Unit) { + for (index in indices) { + action(index, this[index]) + } +} + +inline fun List.noneIndexed(predicate: (Int, T) -> Boolean): Boolean { + for (index in 0 until size) { + if (predicate(index, this[index])) { + return false + } + } + return true +} + +inline fun MutableList.removeAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (predicate(index, this[index])) { + removeAt(index) + } + } +} + +inline fun MutableList.retainAllIndexed(predicate: (Int, T) -> Boolean) { + for (index in lastIndex downTo 0) { + if (!predicate(index, this[index])) { + removeAt(index) + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/data/Package.kt b/services/permission/java/com/android/server/permission/access/data/Package.kt new file mode 100644 index 0000000000000..d6f98ab256cf1 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/data/Package.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.data + +import com.android.server.permission.access.external.AndroidPackage + +class Package( + private val androidPackage: AndroidPackage +) { + val name: String + get() = androidPackage.packageName + + val adoptPermissions: List + get() = androidPackage.adoptPermissions + + val appId: Int + get() = androidPackage.appId + + val requestedPermissions: List + get() = androidPackage.requestedPermissions + + override fun equals(other: Any?): Boolean { + throw NotImplementedError() + } + + override fun hashCode(): Int { + throw NotImplementedError() + } +} diff --git a/services/permission/java/com/android/server/permission/access/data/Permission.kt b/services/permission/java/com/android/server/permission/access/data/Permission.kt new file mode 100644 index 0000000000000..877f23b0e825f --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/data/Permission.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.data + +import android.content.pm.PermissionInfo +import com.android.server.permission.access.util.hasBits + +data class Permission( + val permissionInfo: PermissionInfo, + val isReconciled: Boolean, + val type: Int, + val appId: Int +) { + inline val name: String + get() = permissionInfo.name + + inline val packageName: String + get() = permissionInfo.packageName + + inline val isDynamic: Boolean + get() = type == TYPE_DYNAMIC + + inline val isNormal: Boolean + get() = permissionInfo.protection == PermissionInfo.PROTECTION_NORMAL + + inline val isRuntime: Boolean + get() = permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS + + inline val isSoftRestricted: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED) + + inline val isHardRestricted: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED) + + inline val isSignature: Boolean + get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE + + inline val isInternal: Boolean + get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL + + inline val isDevelopment: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) + + inline val isInstaller: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER) + + inline val isOem: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM) + + inline val isPre23: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRE23) + + inline val isPreInstalled: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PREINSTALLED) + + inline val isPrivileged: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED) + + inline val isSetup: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP) + + inline val isVerifier: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER) + + inline val isVendorPrivileged: Boolean + get() = permissionInfo.protectionFlags + .hasBits(PROTECTION_FLAG_VENDOR_PRIVILEGED) + + inline val isSystemTextClassifier: Boolean + get() = permissionInfo.protectionFlags + .hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) + + inline val isConfigurator: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR) + + inline val isIncidentReportApprover: Boolean + get() = permissionInfo.protectionFlags + .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER) + + inline val isAppPredictor: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) + + inline val isCompanion: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION) + + inline val isRetailDemo: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) + + inline val isRecents: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS) + + inline val isRole: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE) + + inline val isKnownSigner: Boolean + get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) + + inline val protectionLevel: Int + @Suppress("DEPRECATION") + get() = permissionInfo.protectionLevel + + inline val knownCerts: Set + get() = permissionInfo.knownCerts + + companion object { + // The permission is defined in an application manifest. + const val TYPE_MANIFEST = 0 + // The permission is defined in a system config. + const val TYPE_CONFIG = 1 + // The permission is defined dynamically. + const val TYPE_DYNAMIC = 2 + + // TODO: PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED is a testApi + const val PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000 + } +} diff --git a/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt b/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt new file mode 100644 index 0000000000000..aadd8baedc812 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/external/CompatibilityPermissionInfo.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.external + +class CompatibilityPermissionInfo { + companion object { + val COMPAT_PERMS = arrayOf(CompatibilityPermissionInfo()) + } + + val name: String + get() = throw NotImplementedError() + + val sdkVersion: Int + get() = throw NotImplementedError() +} diff --git a/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt b/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt new file mode 100644 index 0000000000000..1239450c0963d --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/external/KnownPackages.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.external + +class KnownPackages { + companion object { + const val PACKAGE_SYSTEM = 0 + const val PACKAGE_SETUP_WIZARD = 1 + const val PACKAGE_INSTALLER = 2 + const val PACKAGE_VERIFIER = 4 + const val PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6 + const val PACKAGE_PERMISSION_CONTROLLER = 7 + const val PACKAGE_CONFIGURATOR = 10 + const val PACKAGE_INCIDENT_REPORT_APPROVER = 11 + const val PACKAGE_APP_PREDICTOR = 12 + const val PACKAGE_COMPANION = 15 + const val PACKAGE_RETAIL_DEMO = 16 + const val PACKAGE_RECENTS = 17 + } +} diff --git a/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt b/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt new file mode 100644 index 0000000000000..24b28bd49fc99 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/external/PackageInfoUtils.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.external + +import android.content.pm.PermissionGroupInfo +import android.content.pm.PermissionInfo + +object PackageInfoUtils { + fun generatePermissionInfo(parsedPermission: ParsedPermission, flags: Long): PermissionInfo { + throw NotImplementedError() + } + + fun generatePermissionGroupInfo( + parsedPermissionGroup: ParsedPermissionGroup, + flags: Long + ): PermissionGroupInfo { + throw NotImplementedError() + } +} diff --git a/services/permission/java/com/android/server/permission/access/external/PackageState.kt b/services/permission/java/com/android/server/permission/access/external/PackageState.kt new file mode 100644 index 0000000000000..b81d7946f2e9a --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/external/PackageState.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.external + +import android.util.SparseArray + +interface PackageState { + val androidPackage: AndroidPackage? + val appId: Int + val isSystem: Boolean + val isUpdatedSystemApp: Boolean + val packageName: String + val userStates: SparseArray + val hasSharedUser: Boolean + val sharedUserAppId: Int + val signingDetails: SigningDetails +} + +interface AndroidPackage { + val packageName: String + val apexModuleName: String? + val appId: Int + val isPrivileged: Boolean + val isOem: Boolean + val isVendor: Boolean + val isProduct: Boolean + val isSystemExt: Boolean + val targetSdkVersion: Int + val adoptPermissions: List + val permissions: List + val permissionGroups: List + val requestedPermissions: List + val implicitPermissions: List +} + +interface ParsedPermission { + val name: String + val isTree: Boolean + val packageName: String + val isSignature: Boolean + val protectionLevel: Int +} + +interface ParsedPermissionGroup { + val name: String + val packageName: String +} + +interface PackageUserState { + val isInstantApp: Boolean +} diff --git a/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt b/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt new file mode 100644 index 0000000000000..528680e77879e --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/external/RoSystemProperties.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.external + +class RoSystemProperties { + companion object { + const val CONTROL_PRIVAPP_PERMISSIONS_DISABLE = false + const val CONTROL_PRIVAPP_PERMISSIONS_ENFORCE = false + } +} diff --git a/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt b/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt new file mode 100644 index 0000000000000..25917f993d4db --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/external/SigningDetails.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.external + +object SigningDetails { + fun hasCommonSignerWithCapability(otherDetails: SigningDetails, flags: Int): Boolean { + throw NotImplementedError() + } + + fun hasAncestorOrSelf(oldDetails: SigningDetails): Boolean { + throw NotImplementedError() + } + + fun checkCapability(oldDetails: SigningDetails, flags: Int): Boolean { + throw NotImplementedError() + } + + fun hasAncestorOrSelfWithDigest(certDigests: Set): Boolean { + throw NotImplementedError() + } + + class CertCapabilities { + companion object { + /** grant SIGNATURE permissions to pkgs with this cert */ + var PERMISSION = 4 + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/external/UserHandle.kt b/services/permission/java/com/android/server/permission/access/external/UserHandle.kt new file mode 100644 index 0000000000000..e1072a760dc22 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/external/UserHandle.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.external + +interface UserHandle { + companion object { + fun getAppId(uid: Int): Int { + throw NotImplementedError() + } + } +} + +object UserHandleCompat { + fun getUserId(uid: Int): Int { + throw NotImplementedError() + } +} diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt new file mode 100644 index 0000000000000..bb1a86c7b32d2 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.permission + +object PermissionFlags { + const val INSTALL_GRANTED = 1 shl 0 + const val INSTALL_REVOKED = 1 shl 1 + const val PROTECTION_GRANTED = 1 shl 2 + const val ROLE_GRANTED = 1 shl 3 + // For permissions that are granted in other ways, + // ex: via an API or implicit permissions that inherit from granted install permissions + const val OTHER_GRANTED = 1 shl 4 + // For the permissions that are implicit for the package + const val IMPLICIT = 1 shl 5 + + const val MASK_GRANTED = INSTALL_GRANTED or PROTECTION_GRANTED or OTHER_GRANTED or ROLE_GRANTED + const val MASK_RUNTIME = OTHER_GRANTED or IMPLICIT +} diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt new file mode 100644 index 0000000000000..3489061c5bae1 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPersistence.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.permission + +import android.content.pm.PermissionInfo +import android.util.Log +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.SystemState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.data.Permission +import com.android.server.permission.access.util.attribute +import com.android.server.permission.access.util.attributeInt +import com.android.server.permission.access.util.attributeIntHex +import com.android.server.permission.access.util.attributeIntHexWithDefault +import com.android.server.permission.access.util.attributeInterned +import com.android.server.permission.access.util.forEachTag +import com.android.server.permission.access.util.getAttributeIntHexOrDefault +import com.android.server.permission.access.util.getAttributeIntHexOrThrow +import com.android.server.permission.access.util.getAttributeIntOrThrow +import com.android.server.permission.access.util.getAttributeValue +import com.android.server.permission.access.util.getAttributeValueOrThrow +import com.android.server.permission.access.util.tag +import com.android.server.permission.access.util.tagName + +class UidPermissionPersistence { + fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) { + when (tagName) { + TAG_PERMISSION_TREES -> parsePermissions(systemState.permissionTrees) + TAG_PERMISSIONS -> parsePermissions(systemState.permissions) + else -> {} + } + } + + private fun BinaryXmlPullParser.parsePermissions(permissions: IndexedMap) { + forEachTag { + when (val tagName = tagName) { + TAG_PERMISSION -> parsePermission(permissions) + else -> Log.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing permissions") + } + } + } + + private fun BinaryXmlPullParser.parsePermission(permissions: IndexedMap) { + val name = getAttributeValueOrThrow(ATTR_NAME).intern() + @Suppress("DEPRECATION") + val permissionInfo = PermissionInfo().apply { + this.name = name + packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() + protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) + } + val type = getAttributeIntOrThrow(ATTR_TYPE) + when (type) { + Permission.TYPE_MANIFEST -> {} + Permission.TYPE_CONFIG -> { + Log.w(LOG_TAG, "Ignoring unexpected config permission $name") + return + } + Permission.TYPE_DYNAMIC -> { + permissionInfo.apply { + icon = getAttributeIntHexOrDefault(ATTR_ICON, 0) + nonLocalizedLabel = getAttributeValue(ATTR_LABEL) + } + } + else -> { + Log.w(LOG_TAG, "Ignoring permission $name with unknown type $type") + return + } + } + val permission = Permission(permissionInfo, false, type, 0) + permissions[name] = permission + } + + fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) { + serializePermissions(TAG_PERMISSION_TREES, systemState.permissionTrees) + serializePermissions(TAG_PERMISSIONS, systemState.permissions) + } + + private fun BinaryXmlSerializer.serializePermissions( + tagName: String, + permissions: IndexedMap + ) { + tag(tagName) { + permissions.forEachValueIndexed { _, it -> serializePermission(it) } + } + } + + private fun BinaryXmlSerializer.serializePermission(permission: Permission) { + val type = permission.type + when (type) { + Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {} + Permission.TYPE_CONFIG -> return + else -> { + Log.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type") + return + } + } + tag(TAG_PERMISSION) { + attributeInterned(ATTR_NAME, permission.name) + attributeInterned(ATTR_PACKAGE_NAME, permission.packageName) + attributeIntHex(ATTR_PROTECTION_LEVEL, permission.protectionLevel) + attributeInt(ATTR_TYPE, type) + if (type == Permission.TYPE_DYNAMIC) { + val permissionInfo = permission.permissionInfo + attributeIntHexWithDefault(ATTR_ICON, permissionInfo.icon, 0) + permissionInfo.nonLocalizedLabel?.toString()?.let { attribute(ATTR_LABEL, it) } + } + } + } + + companion object { + private val LOG_TAG = UidPermissionPersistence::class.java.simpleName + + private const val TAG_PERMISSION = "permission" + private const val TAG_PERMISSION_TREES = "permission-trees" + private const val TAG_PERMISSIONS = "permissions" + + private const val ATTR_ICON = "icon" + private const val ATTR_LABEL = "label" + private const val ATTR_NAME = "name" + private const val ATTR_PACKAGE_NAME = "packageName" + private const val ATTR_PROTECTION_LEVEL = "protectionLevel" + private const val ATTR_TYPE = "type" + } +} diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt new file mode 100644 index 0000000000000..6479e6ad88e39 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt @@ -0,0 +1,909 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.permission + +import android.Manifest +import android.content.pm.PackageManager +import android.content.pm.PermissionInfo +import android.os.Build +import android.os.UserHandle +import android.util.Log +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.AccessState +import com.android.server.permission.access.AccessUri +import com.android.server.permission.access.PermissionUri +import com.android.server.permission.access.SchemePolicy +import com.android.server.permission.access.SystemState +import com.android.server.permission.access.UidUri +import com.android.server.permission.access.UserState +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.data.Permission +import com.android.server.permission.access.external.AndroidPackage +import com.android.server.permission.access.external.CompatibilityPermissionInfo +import com.android.server.permission.access.external.KnownPackages +import com.android.server.permission.access.external.PackageInfoUtils +import com.android.server.permission.access.external.PackageState +import com.android.server.permission.access.external.RoSystemProperties +import com.android.server.permission.access.external.SigningDetails +import com.android.server.permission.access.util.hasAnyBit +import com.android.server.permission.access.util.hasBits + +class UidPermissionPolicy : SchemePolicy() { + private val persistence = UidPermissionPersistence() + + override val subjectScheme: String + get() = UidUri.SCHEME + + override val objectScheme: String + get() = PermissionUri.SCHEME + + override fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int { + subject as UidUri + `object` as PermissionUri + return state.userStates[subject.userId]?.permissionFlags?.get(subject.appId) + ?.get(`object`.permissionName) ?: 0 + } + + override fun setDecision( + subject: AccessUri, + `object`: AccessUri, + decision: Int, + oldState: AccessState, + newState: AccessState + ) { + subject as UidUri + `object` as PermissionUri + val uidFlags = newState.userStates.getOrPut(subject.userId) { UserState() } + .permissionFlags.getOrPut(subject.appId) { IndexedMap() } + uidFlags[`object`.permissionName] = decision + } + + override fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) { + newState.systemState.packageStates.forEachValueIndexed { _, packageState -> + evaluateAllPermissionStatesForPackageAndUser( + packageState, null, userId, oldState, newState + ) + grantImplicitPermissions(packageState, userId, oldState, newState) + } + } + + override fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) { + newState.userStates.forEachIndexed { _, _, userState -> + userState.permissionFlags.getOrPut(appId) { IndexedMap() } + } + } + + override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) { + newState.userStates.forEachIndexed { _, _, userState -> userState.permissionFlags -= appId } + } + + override fun onPackageAdded( + packageState: PackageState, + oldState: AccessState, + newState: AccessState + ) { + val changedPermissionNames = IndexedSet() + adoptPermissions(packageState, changedPermissionNames, newState) + addPermissionGroups(packageState, newState) + addPermissions(packageState, changedPermissionNames, newState) + // TODO: revokeStoragePermissionsIfScopeExpandedInternal() + trimPermissions(packageState.packageName, newState) + changedPermissionNames.forEachIndexed { _, it -> + evaluatePermissionStateForAllPackages(it, packageState, oldState, newState) + } + + evaluateAllPermissionStatesForPackage(packageState, packageState, oldState, newState) + newState.systemState.userIds.forEachIndexed { _, it -> + grantImplicitPermissions(packageState, it, oldState, newState) + } + + // TODO: add trimPermissionStates() here for removing the permission states that are + // no longer requested. (equivalent to revokeUnusedSharedUserPermissionsLocked()) + } + + private fun adoptPermissions( + packageState: PackageState, + changedPermissionNames: IndexedSet, + newState: AccessState + ) { + val `package` = packageState.androidPackage!! + `package`.adoptPermissions.forEachIndexed { _, originalPackageName -> + val packageName = `package`.packageName + if (!canAdoptPermissions(packageName, originalPackageName, newState)) { + return@forEachIndexed + } + newState.systemState.permissions.let { permissions -> + permissions.forEachIndexed { i, permissionName, oldPermission -> + if (oldPermission.packageName != originalPackageName) { + return@forEachIndexed + } + @Suppress("DEPRECATION") + val newPermissionInfo = PermissionInfo().apply { + name = oldPermission.permissionInfo.name + this.packageName = packageName + protectionLevel = oldPermission.permissionInfo.protectionLevel + } + val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0) + changedPermissionNames += permissionName + permissions.setValueAt(i, newPermission) + } + } + } + } + + private fun canAdoptPermissions( + packageName: String, + originalPackageName: String, + newState: AccessState + ): Boolean { + val originalPackageState = newState.systemState.packageStates[originalPackageName] + ?: return false + if (!originalPackageState.isSystem) { + Log.w( + LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + " original package not in system partition" + ) + return false + } + if (originalPackageState.androidPackage != null) { + Log.w( + LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + " original package still exists" + ) + return false + } + return true + } + + private fun addPermissionGroups(packageState: PackageState, newState: AccessState) { + // Different from the old implementation, which decides whether the app is an instant app by + // the install flags, now for consistent behavior we allow adding permission groups if the + // app is non-instant in at least one user. + val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp } + if (isInstantApp) { + Log.w( + LOG_TAG, "Ignoring permission groups declared in package" + + " ${packageState.packageName}: instant apps cannot declare permission groups" + ) + return + } + packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup -> + val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo( + parsedPermissionGroup, PackageManager.GET_META_DATA.toLong() + ) + // TODO: Clear permission state on group take-over? + val permissionGroupName = newPermissionGroup.name + val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName] + if (oldPermissionGroup != null && + newPermissionGroup.packageName != oldPermissionGroup.packageName) { + Log.w( + LOG_TAG, "Ignoring permission group $permissionGroupName declared in package" + + " ${newPermissionGroup.packageName}: already declared in another package" + + " ${oldPermissionGroup.packageName}" + ) + return@forEachIndexed + } + newState.systemState.permissionGroups[permissionGroupName] = newPermissionGroup + } + } + + private fun addPermissions( + packageState: PackageState, + changedPermissionNames: IndexedSet, + newState: AccessState + ) { + packageState.androidPackage!!.permissions.forEachIndexed { _, parsedPermission -> + // TODO: + // parsedPermission.flags = parsedPermission.flags andInv PermissionInfo.FLAG_INSTALLED + // TODO: This seems actually unused. + // if (packageState.androidPackage.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { + // parsedPermission.setParsedPermissionGroup( + // newState.systemState.permissionGroup[parsedPermission.group] + // ) + // } + val newPermissionInfo = PackageInfoUtils.generatePermissionInfo( + parsedPermission, PackageManager.GET_META_DATA.toLong() + ) + // TODO: newPermissionInfo.flags |= PermissionInfo.FLAG_INSTALLED + val permissionName = newPermissionInfo.name + val oldPermission = if (parsedPermission.isTree) { + newState.systemState.permissionTrees[permissionName] + } else { + newState.systemState.permissions[permissionName] + } + // Different from the old implementation, which may add an (incomplete) signature + // permission inside another package's permission tree, we now consistently ignore such + // permissions. + val permissionTree = getPermissionTree(permissionName, newState) + val newPackageName = newPermissionInfo.packageName + if (permissionTree != null && newPackageName != permissionTree.packageName) { + Log.w( + LOG_TAG, "Ignoring permission $permissionName declared in package" + + " $newPackageName: base permission tree ${permissionTree.name} is" + + " declared in another package ${permissionTree.packageName}" + ) + return@forEachIndexed + } + val newPermission = if (oldPermission != null && + newPackageName != oldPermission.packageName) { + val oldPackageName = oldPermission.packageName + // Only allow system apps to redefine non-system permissions. + if (!packageState.isSystem) { + Log.w( + LOG_TAG, "Ignoring permission $permissionName declared in package" + + " $newPackageName: already declared in another package" + + " $oldPackageName" + ) + return@forEachIndexed + } + if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) { + // It's a config permission and has no owner, take ownership now. + Permission(newPermissionInfo, true, Permission.TYPE_CONFIG, packageState.appId) + } else if (newState.systemState.packageStates[oldPackageName]?.isSystem != true) { + Log.w( + LOG_TAG, "Overriding permission $permissionName with new declaration in" + + " system package $newPackageName: originally declared in another" + + " package $oldPackageName" + ) + // Remove permission state on owner change. + newState.userStates.forEachValueIndexed { _, userState -> + userState.permissionFlags.forEachValueIndexed { _, permissionFlags -> + permissionFlags -= newPermissionInfo.name + } + } + // TODO: Notify re-evaluation of this permission. + Permission( + newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId + ) + } else { + Log.w( + LOG_TAG, "Ignoring permission $permissionName declared in system package" + + " $newPackageName: already declared in another system package" + + " $oldPackageName") + return@forEachIndexed + } + } else { + // TODO: STOPSHIP: Clear permission state on type or group change. + // Different from the old implementation, which doesn't update the permission + // definition upon app update, but does update it on the next boot, we now + // consistently update the permission definition upon app update. + Permission(newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId) + } + + changedPermissionNames += permissionName + if (parsedPermission.isTree) { + newState.systemState.permissionTrees[permissionName] = newPermission + } else { + newState.systemState.permissions[permissionName] = newPermission + } + } + } + + private fun trimPermissions( + packageName: String, + newState: AccessState, + ) { + val packageState = newState.systemState.packageStates[packageName] + val androidPackage = packageState?.androidPackage + if (packageState != null && androidPackage == null) { + return + } + + newState.systemState.permissionTrees.removeAllIndexed { + _, permissionTreeName, permissionTree -> + permissionTree.packageName == packageName && ( + packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> + it.isTree && it.name == permissionTreeName + } + ) + } + + newState.systemState.permissions.removeAllIndexed { i, permissionName, permission -> + val updatedPermission = updatePermissionIfDynamic(permission, newState) + newState.systemState.permissions.setValueAt(i, updatedPermission) + if (updatedPermission.packageName == packageName && ( + packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> + !it.isTree && it.name == permissionName + } + )) { + if (!isPermissionDeclaredByDisabledSystemPackage(permission, newState)) { + newState.userStates.forEachIndexed { _, userId, userState -> + userState.permissionFlags.forEachKeyIndexed { _, appId -> + setPermissionFlags( + appId, permissionName, getPermissionFlags( + appId, permissionName, userId, newState + ) and PermissionFlags.INSTALL_REVOKED, userId, newState + ) + } + } + } + true + } else { + false + } + } + } + + private fun isPermissionDeclaredByDisabledSystemPackage( + permission: Permission, + newState: AccessState + ): Boolean { + val disabledSystemPackage = newState.systemState + .disabledSystemPackageStates[permission.packageName]?.androidPackage ?: return false + return disabledSystemPackage.permissions.anyIndexed { _, it -> + it.name == permission.name && it.protectionLevel == permission.protectionLevel + } + } + + private fun updatePermissionIfDynamic( + permission: Permission, + newState: AccessState + ): Permission { + if (!permission.isDynamic) { + return permission + } + val permissionTree = getPermissionTree(permission.name, newState) ?: return permission + @Suppress("DEPRECATION") + return permission.copy( + permissionInfo = PermissionInfo(permission.permissionInfo).apply { + packageName = permissionTree.packageName + }, appId = permissionTree.appId, isReconciled = true + ) + } + + private fun getPermissionTree(permissionName: String, newState: AccessState): Permission? = + newState.systemState.permissionTrees.firstNotNullOfOrNullIndexed { + _, permissionTreeName, permissionTree -> + if (permissionName.startsWith(permissionTreeName) && + permissionName.length > permissionTreeName.length && + permissionName[permissionTreeName.length] == '.') { + permissionTree + } else { + null + } + } + + private fun evaluatePermissionStateForAllPackages( + permissionName: String, + installedPackageState: PackageState?, + oldState: AccessState, + newState: AccessState + ) { + newState.systemState.userIds.forEachIndexed { _, userId -> + oldState.userStates[userId]?.permissionFlags?.forEachIndexed { + _, appId, permissionFlags -> + if (permissionName in permissionFlags) { + evaluatePermissionState( + appId, permissionName, installedPackageState, userId, oldState, newState + ) + } + } + } + } + + private fun evaluateAllPermissionStatesForPackage( + packageState: PackageState, + installedPackageState: PackageState?, + oldState: AccessState, + newState: AccessState + ) { + newState.systemState.userIds.forEachIndexed { _, userId -> + evaluateAllPermissionStatesForPackageAndUser( + packageState, installedPackageState, userId, oldState, newState + ) + } + } + + private fun evaluateAllPermissionStatesForPackageAndUser( + packageState: PackageState, + installedPackageState: PackageState?, + userId: Int, + oldState: AccessState, + newState: AccessState + ) { + packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, it -> + evaluatePermissionState( + packageState.appId, it, installedPackageState, userId, oldState, newState + ) + } + } + + private fun evaluatePermissionState( + appId: Int, + permissionName: String, + installedPackageState: PackageState?, + userId: Int, + oldState: AccessState, + newState: AccessState + ) { + val packageNames = newState.systemState.appIds[appId] + val hasMissingPackage = packageNames.anyIndexed { _, packageName -> + newState.systemState.packageStates[packageName]!!.androidPackage == null + } + if (packageNames.size == 1 && hasMissingPackage) { + // For non-shared-user packages with missing androidPackage, skip evaluation. + return + } + val permission = newState.systemState.permissions[permissionName] ?: return + val oldFlags = getPermissionFlags(appId, permissionName, userId, newState) + if (permission.isNormal) { + val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) + if (!wasGranted) { + val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) + val isRequestedByInstalledPackage = installedPackageState != null && + permissionName in installedPackageState.androidPackage!!.requestedPermissions + val isRequestedBySystemPackage = anyPackageInAppId(appId, newState) { + it.isSystem && permissionName in it.androidPackage!!.requestedPermissions + } + val isCompatibilityPermission = anyPackageInAppId(appId, newState) { + isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) + } + // If this is an existing, non-system package, + // then we can't add any new permissions to it. + // Except if this is a permission that was added to the platform + val newFlags = if (!wasRevoked || isRequestedByInstalledPackage || + isRequestedBySystemPackage || isCompatibilityPermission) { + PermissionFlags.INSTALL_GRANTED + } else { + PermissionFlags.INSTALL_REVOKED + } + setPermissionFlags(appId, permissionName, newFlags, userId, newState) + } + } else if (permission.isSignature || permission.isInternal) { + val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) + var newFlags = if (hasMissingPackage && wasProtectionGranted) { + // Keep the non-runtime permission grants for shared UID with missing androidPackage + PermissionFlags.PROTECTION_GRANTED + } else { + val mayGrantByPrivileged = !permission.isPrivileged || ( + anyPackageInAppId(appId, newState) { + checkPrivilegedPermissionAllowlist(it, permission, newState) + } + ) + val shouldGrantBySignature = permission.isSignature && ( + anyPackageInAppId(appId, newState) { + shouldGrantPermissionBySignature(it, permission, newState) + } + ) + val shouldGrantByProtectionFlags = anyPackageInAppId(appId, newState) { + shouldGrantPermissionByProtectionFlags(it, permission, newState) + } + if (mayGrantByPrivileged && + (shouldGrantBySignature || shouldGrantByProtectionFlags)) { + PermissionFlags.PROTECTION_GRANTED + } else { + 0 + } + } + // Different from the old implementation, which seemingly allows granting an + // unallowlisted privileged permission via development or role but revokes it upon next + // reconciliation, we now properly allows that because the privileged protection flag + // should only affect the other static flags, but not dynamic flags like development or + // role. This may be useful in the case of an updated system app. + if (permission.isDevelopment) { + newFlags = newFlags or (oldFlags and PermissionFlags.OTHER_GRANTED) + } + if (permission.isRole) { + newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED) + } + setPermissionFlags(appId, permissionName, newFlags, userId, newState) + } else if (permission.isRuntime) { + // TODO: add runtime permissions + } else { + Log.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" + + "for permission ${permission.name} while evaluating permission state" + + "for appId $appId and userId $userId") + } + + // TODO: revokePermissionsNoLongerImplicitLocked() for runtime permissions + } + + private fun grantImplicitPermissions( + packageState: PackageState, + userId: Int, + oldState: AccessState, + newState: AccessState + ) { + val appId = packageState.appId + val androidPackage = packageState.androidPackage ?: return + androidPackage.implicitPermissions.forEachIndexed implicitPermissions@ { + _, implicitPermissionName -> + val implicitPermission = newState.systemState.permissions[implicitPermissionName] + checkNotNull(implicitPermission) { + "Unknown implicit permission $implicitPermissionName in split permissions" + } + if (!implicitPermission.isRuntime) { + return@implicitPermissions + } + val isNewPermission = getPermissionFlags( + appId, implicitPermissionName, userId, oldState + ) == 0 + if (!isNewPermission) { + return@implicitPermissions + } + val sourcePermissions = newState.systemState + .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions + var newFlags = 0 + sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName -> + val sourcePermission = newState.systemState.permissions[sourcePermissionName] + checkNotNull(sourcePermission) { + "Unknown source permission $sourcePermissionName in split permissions" + } + val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId, newState) + val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED) + val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED) + val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted + if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) { + if (isGrantingNewFromRevoke) { + newFlags = 0 + } + newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME) + if (!sourcePermission.isRuntime && isSourceGranted) { + newFlags = newFlags or PermissionFlags.OTHER_GRANTED + } + } + } + newFlags = newFlags or PermissionFlags.IMPLICIT + setPermissionFlags(appId, implicitPermissionName, newFlags, userId, newState) + } + } + + private fun getPermissionFlags( + appId: Int, + permissionName: String, + userId: Int, + state: AccessState + ): Int = state.userStates[userId].permissionFlags[appId].getWithDefault(permissionName, 0) + + private fun setPermissionFlags( + appId: Int, + permissionName: String, + flags: Int, + userId: Int, + newState: AccessState + ) { + newState.userStates[userId].permissionFlags[appId]!! + .putWithDefault(permissionName, flags, 0) + } + + private fun isCompatibilityPermissionForPackage( + androidPackage: AndroidPackage, + permissionName: String + ): Boolean { + for (info: CompatibilityPermissionInfo in CompatibilityPermissionInfo.COMPAT_PERMS) { + if (info.name == permissionName && androidPackage.targetSdkVersion < info.sdkVersion) { + Log.i( + LOG_TAG, "Auto-granting $permissionName to old package" + + " ${androidPackage.packageName}" + ) + return true + } + } + return false + } + + private fun shouldGrantPermissionBySignature( + packageState: PackageState, + permission: Permission, + newState: AccessState + ): Boolean { + // check if the package is allow to use this signature permission. A package is allowed to + // use a signature permission if: + // - it has the same set of signing certificates as the source package + // - or its signing certificate was rotated from the source package's certificate + // - or its signing certificate is a previous signing certificate of the defining + // package, and the defining package still trusts the old certificate for permissions + // - or it shares a common signing certificate in its lineage with the defining package, + // and the defining package still trusts the old certificate for permissions + // - or it shares the above relationships with the system package + val sourceSigningDetails = newState.systemState + .packageStates[permission.packageName]?.signingDetails + val platformSigningDetails = newState.systemState + .packageStates[PLATFORM_PACKAGE_NAME]!!.signingDetails + return sourceSigningDetails?.hasCommonSignerWithCapability(packageState.signingDetails, + SigningDetails.CertCapabilities.PERMISSION) == true || + packageState.signingDetails.hasAncestorOrSelf(platformSigningDetails) || + platformSigningDetails.checkCapability(packageState.signingDetails, + SigningDetails.CertCapabilities.PERMISSION) + } + + private fun checkPrivilegedPermissionAllowlist( + packageState: PackageState, + permission: Permission, + newState: AccessState + ): Boolean { + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) { + return true + } + if (packageState.packageName == PLATFORM_PACKAGE_NAME) { + return true + } + val androidPackage = packageState.androidPackage!! + if (!androidPackage.isPrivileged) { + return true + } + if (permission.packageName !in + newState.systemState.privilegedPermissionAllowlistSourcePackageNames) { + return true + } + if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name, newState)) { + return true + } + if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name, newState)) { + return false + } + // Updated system apps do not need to be allowlisted + if (packageState.isUpdatedSystemApp) { + return true + } + // TODO: Enforce the allowlist on boot + return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE + } + + private fun isInSystemConfigPrivAppPermissions( + androidPackage: AndroidPackage, + permissionName: String, + newState: AccessState + ): Boolean { + val apexModuleName = androidPackage.apexModuleName + val systemState = newState.systemState + val packageName = androidPackage.packageName + val permissionNames = when { + androidPackage.isVendor -> systemState.vendorPrivAppPermissions[packageName] + androidPackage.isProduct -> systemState.productPrivAppPermissions[packageName] + androidPackage.isSystemExt -> systemState.systemExtPrivAppPermissions[packageName] + apexModuleName != null -> { + val apexPrivAppPermissions = systemState.apexPrivAppPermissions[apexModuleName] + ?.get(packageName) + val privAppPermissions = systemState.privAppPermissions[packageName] + when { + apexPrivAppPermissions == null -> privAppPermissions + privAppPermissions == null -> apexPrivAppPermissions + else -> apexPrivAppPermissions + privAppPermissions + } + } + else -> systemState.privAppPermissions[packageName] + } + return permissionNames?.contains(permissionName) == true + } + + private fun isInSystemConfigPrivAppDenyPermissions( + androidPackage: AndroidPackage, + permissionName: String, + newState: AccessState + ): Boolean { + // Different from the previous implementation, which may incorrectly use the APEX package + // name, we now use the APEX module name to be consistent with the allowlist. + val apexModuleName = androidPackage.apexModuleName + val systemState = newState.systemState + val packageName = androidPackage.packageName + val permissionNames = when { + androidPackage.isVendor -> systemState.vendorPrivAppDenyPermissions[packageName] + androidPackage.isProduct -> systemState.productPrivAppDenyPermissions[packageName] + androidPackage.isSystemExt -> systemState.systemExtPrivAppDenyPermissions[packageName] + // Different from the previous implementation, which ignores the regular priv app + // denylist in this case, we now respect it as well to be consistent with the allowlist. + apexModuleName != null -> { + val apexPrivAppDenyPermissions = systemState + .apexPrivAppDenyPermissions[apexModuleName]?.get(packageName) + val privAppDenyPermissions = systemState.privAppDenyPermissions[packageName] + when { + apexPrivAppDenyPermissions == null -> privAppDenyPermissions + privAppDenyPermissions == null -> apexPrivAppDenyPermissions + else -> apexPrivAppDenyPermissions + privAppDenyPermissions + } + } + else -> systemState.privAppDenyPermissions[packageName] + } + return permissionNames?.contains(permissionName) == true + } + + private fun anyPackageInAppId( + appId: Int, + newState: AccessState, + predicate: (PackageState) -> Boolean + ): Boolean { + val packageNames = newState.systemState.appIds[appId] + return packageNames.anyIndexed { _, packageName -> + val packageState = newState.systemState.packageStates[packageName]!! + packageState.androidPackage != null && predicate(packageState) + } + } + + private fun shouldGrantPermissionByProtectionFlags( + packageState: PackageState, + permission: Permission, + newState: AccessState + ): Boolean { + val androidPackage = packageState.androidPackage!! + val knownPackages = newState.systemState.knownPackages + val packageName = packageState.packageName + if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) { + val shouldGrant = if (packageState.isUpdatedSystemApp) { + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + val disabledSystemPackage = newState.systemState + .disabledSystemPackageStates[packageState.packageName]?.androidPackage + disabledSystemPackage != null && + permission.name in disabledSystemPackage.requestedPermissions && + shouldGrantPrivilegedOrOemPermission( + disabledSystemPackage, permission, newState + ) + } else { + shouldGrantPrivilegedOrOemPermission(androidPackage, permission, newState) + } + if (shouldGrant) { + return true + } + } + if (permission.isPre23 && androidPackage.targetSdkVersion < Build.VERSION_CODES.M) { + // If this was a previously normal/dangerous permission that got moved + // to a system permission as part of the runtime permission redesign, then + // we still want to blindly grant it to old apps. + return true + } + if (permission.isInstaller && ( + packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER] || + packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] + )) { + // If this permission is to be granted to the system installer and + // this app is an installer or permission controller, then it gets the permission. + return true + } + if (permission.isVerifier && + packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]) { + // If this permission is to be granted to the system verifier and + // this app is a verifier, then it gets the permission. + return true + } + if (permission.isPreInstalled && packageState.isSystem) { + // Any pre-installed system app is allowed to get this permission. + return true + } + if (permission.isKnownSigner && + packageState.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) { + // If the permission is to be granted to a known signer then check if any of this + // app's signing certificates are in the trusted certificate digest Set. + return true + } + if (permission.isSetup && + packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]) { + // If this permission is to be granted to the system setup wizard and + // this app is a setup wizard, then it gets the permission. + return true + } + if (permission.isSystemTextClassifier && + packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]) { + // Special permissions for the system default text classifier. + return true + } + if (permission.isConfigurator && + packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]) { + // Special permissions for the device configurator. + return true + } + if (permission.isIncidentReportApprover && + packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]) { + // If this permission is to be granted to the incident report approver and + // this app is the incident report approver, then it gets the permission. + return true + } + if (permission.isAppPredictor && + packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]) { + // Special permissions for the system app predictor. + return true + } + if (permission.isCompanion && + packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]) { + // Special permissions for the system companion device manager. + return true + } + if (permission.isRetailDemo && + packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO] && + isDeviceOrProfileOwnerUid(packageState.appId, newState)) { + // Special permission granted only to the OEM specified retail demo app. + // Note that the original code was passing app ID as UID, so this behavior is kept + // unchanged. + return true + } + if (permission.isRecents && + packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]) { + // Special permission for the recents app. + return true + } + return false + } + + private fun shouldGrantPrivilegedOrOemPermission( + androidPackage: AndroidPackage, + permission: Permission, + state: AccessState + ): Boolean { + val permissionName = permission.name + val packageName = androidPackage.packageName + when { + permission.isPrivileged -> { + if (androidPackage.isPrivileged) { + // In any case, don't grant a privileged permission to privileged vendor apps, + // if the permission's protectionLevel does not have the extra vendorPrivileged + // flag. + if (androidPackage.isVendor && !permission.isVendorPrivileged) { + Log.w( + LOG_TAG, "Permission $permissionName cannot be granted to privileged" + + " vendor app $packageName because it isn't a vendorPrivileged" + + " permission" + ) + return false + } + return true + } + } + permission.isOem -> { + if (androidPackage.isOem) { + val isOemAllowlisted = state.systemState + .oemPermissions[packageName]?.get(permissionName) + checkNotNull(isOemAllowlisted) { + "OEM permission $permissionName requested by package" + + " $packageName must be explicitly declared granted or not" + } + return isOemAllowlisted + } + } + } + return false + } + + private fun isDeviceOrProfileOwnerUid(uid: Int, state: AccessState): Boolean { + val userId = UserHandle.getUserId(uid) + val ownerPackageName = state.systemState.deviceAndProfileOwners[userId] ?: return false + val ownerPackageState = state.systemState.packageStates[ownerPackageName] ?: return false + val ownerUid = UserHandle.getUid(userId, ownerPackageState.appId) + return uid == ownerUid + } + + override fun onPackageRemoved( + packageState: PackageState, + oldState: AccessState, + newState: AccessState + ) { + // TODO + } + + override fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) { + with(persistence) { this@parseSystemState.parseSystemState(systemState) } + } + + override fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) { + with(persistence) { this@serializeSystemState.serializeSystemState(systemState) } + } + + companion object { + private val LOG_TAG = UidPermissionPolicy::class.java.simpleName + + private const val PLATFORM_PACKAGE_NAME = "android" + + // A set of permissions that we don't want to revoke when they are no longer implicit. + private val RETAIN_IMPLICIT_GRANT_PERMISSIONS = indexedSetOf( + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.ACTIVITY_RECOGNITION, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + ) + } +} diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt new file mode 100644 index 0000000000000..984dfb5a7548b --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.util + +import android.util.AtomicFile +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException + +/** + * Read from an [AtomicFile] and close everything safely when done. + */ +@Throws(IOException::class) +inline fun AtomicFile.read(block: (FileInputStream) -> Unit) { + openRead().use(block) +} + +/** + * Write to an [AtomicFile] and close everything safely when done. + */ +@Throws(IOException::class) +// Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline. +inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) { + startWrite().use { + try { + block(it) + finishWrite(it) + } catch (t: Throwable) { + failWrite(it) + throw t + } + } +} diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt new file mode 100644 index 0000000000000..1d27aef39a2c0 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.util + +import com.android.modules.utils.BinaryXmlPullParser +import java.io.IOException +import java.io.InputStream +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException + +/** + * Parse content from [InputStream] with [BinaryXmlPullParser]. + */ +@Throws(IOException::class, XmlPullParserException::class) +inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { + BinaryXmlPullParser().apply { + setInput(this@parseBinaryXml, null) + block() + } +} + +/** + * Iterate through child tags of the current tag. + *

+ * Attributes for the current tag needs to be accessed before this method is called because this + * method will advance the parser past the start tag of the current tag. The code inspecting each + * child tag may access the attributes of the child tag, and/or call [forEachTag] recursively to + * inspect grandchild tags, which will naturally leave the parser at either the start tag or the end + * tag of the child tag it inspected. + * + * @see BinaryXmlPullParser.next + * @see BinaryXmlPullParser.getEventType + * @see BinaryXmlPullParser.getDepth + */ +@Throws(IOException::class, XmlPullParserException::class) +inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) { + when (val eventType = eventType) { + // Document start or start tag of the parent tag. + XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd() + else -> throw XmlPullParserException("Unexpected event type $eventType") + } + while (true) { + when (val eventType = eventType) { + // Start tag of a child tag. + XmlPullParser.START_TAG -> { + val childDepth = depth + block() + // block() should leave the parser at either the start tag (no grandchild tags + // expected) or the end tag (grandchild tags parsed with forEachTag()) of this child + // tag. + val postBlockDepth = depth + if (postBlockDepth != childDepth) { + throw XmlPullParserException( + "Unexpected post-block depth $postBlockDepth, expected $childDepth" + ) + } + // Skip the parser to the end tag of this child tag. + while (true) { + when (val childEventType = this.eventType) { + // Start tag of either this child tag or a grandchild tag. + XmlPullParser.START_TAG -> nextTagOrEnd() + XmlPullParser.END_TAG -> { + if (depth > childDepth) { + // End tag of a grandchild tag. + nextTagOrEnd() + } else { + // End tag of this child tag. + break + } + } + else -> + throw XmlPullParserException("Unexpected event type $childEventType") + } + } + // Skip the end tag of this child tag. + nextTagOrEnd() + } + // End tag of the parent tag, or document end. + XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break + else -> throw XmlPullParserException("Unexpected event type $eventType") + } + } +} + +/** + * Advance the parser until the current event is one of [XmlPullParser.START_TAG], + * [XmlPullParser.START_TAG] and [XmlPullParser.START_TAG] + * + * @see BinaryXmlPullParser.next + */ +@Throws(IOException::class, XmlPullParserException::class) +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.nextTagOrEnd(): Int { + while (true) { + when (val eventType = next()) { + XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> + return eventType + else -> continue + } + } +} + +/** + * @see BinaryXmlPullParser.getName + */ +inline val BinaryXmlPullParser.tagName: String + get() = name + +/** + * Check whether an attribute exists for the current tag. + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1 + +/** + * @see BinaryXmlPullParser.getAttributeIndex + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeIndexOrThrow + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int = + getAttributeIndexOrThrow(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeValue + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? = + getAttributeValue(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeValue + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String = + getAttributeValue(getAttributeIndexOrThrow(name)) + +/** + * @see BinaryXmlPullParser.getAttributeBytesHex + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? = + getAttributeBytesHex(null, name, null) + +/** + * @see BinaryXmlPullParser.getAttributeBytesHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray = + getAttributeBytesHex(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeBytesBase64 + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? = + getAttributeBytesBase64(null, name, null) + +/** + * @see BinaryXmlPullParser.getAttributeBytesBase64 + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray = + getAttributeBytesBase64(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeInt + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int = + getAttributeInt(null, name, defaultValue) + +/** + * @see BinaryXmlPullParser.getAttributeInt + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int = + getAttributeInt(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeIntHex + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int = + getAttributeIntHex(null, name, defaultValue) + +/** + * @see BinaryXmlPullParser.getAttributeIntHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int = + getAttributeIntHex(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeLong + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long = + getAttributeLong(null, name, defaultValue) + +/** + * @see BinaryXmlPullParser.getAttributeLong + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long = + getAttributeLong(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeLongHex + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault( + name: String, + defaultValue: Long +): Long = getAttributeLongHex(null, name, defaultValue) + +/** + * @see BinaryXmlPullParser.getAttributeLongHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long = + getAttributeLongHex(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeFloat + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeFloatOrDefault( + name: String, + defaultValue: Float +): Float = getAttributeFloat(null, name, defaultValue) + +/** + * @see BinaryXmlPullParser.getAttributeFloat + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float = + getAttributeFloat(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeDouble + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault( + name: String, + defaultValue: Double +): Double = getAttributeDouble(null, name, defaultValue) + +/** + * @see BinaryXmlPullParser.getAttributeDouble + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double = + getAttributeDouble(null, name) + +/** + * @see BinaryXmlPullParser.getAttributeBoolean + */ +@Suppress("NOTHING_TO_INLINE") +inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault( + name: String, + defaultValue: Boolean +): Boolean = getAttributeBoolean(null, name, defaultValue) + +/** + * @see BinaryXmlPullParser.getAttributeBoolean + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(XmlPullParserException::class) +inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean = + getAttributeBoolean(null, name) diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt new file mode 100644 index 0000000000000..c8cd5866adbc7 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.util + +import com.android.modules.utils.BinaryXmlSerializer +import java.io.IOException +import java.io.OutputStream + +/** + * Serialize content into [OutputStream] with [BinaryXmlSerializer]. + */ +@Throws(IOException::class) +inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) { + BinaryXmlSerializer().apply { + setOutput(this@serializeBinaryXml, null) + document(block) + } +} + +/** + * Write a document with [BinaryXmlSerializer]. + * + * @see BinaryXmlSerializer.startDocument + * @see BinaryXmlSerializer.endDocument + */ +@Throws(IOException::class) +inline fun BinaryXmlSerializer.document(block: BinaryXmlSerializer.() -> Unit) { + startDocument(null, true) + block() + endDocument() +} + +/** + * Write a tag with [BinaryXmlSerializer]. + * + * @see BinaryXmlSerializer.startTag + * @see BinaryXmlSerializer.endTag + */ +@Throws(IOException::class) +inline fun BinaryXmlSerializer.tag(name: String, block: BinaryXmlSerializer.() -> Unit) { + startTag(null, name) + block() + endTag(null, name) +} + +/** + * @see BinaryXmlSerializer.attribute + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attribute(name: String, value: String) { + attribute(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeInterned + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) { + attributeInterned(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeBytesHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) { + attributeBytesHex(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeBytesBase64 + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) { + attributeBytesBase64(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeInt + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) { + attributeInt(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeInt + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeIntWithDefault( + name: String, + value: Int, + defaultValue: Int +) { + if (value != defaultValue) { + attributeInt(null, name, value) + } +} + +/** + * @see BinaryXmlSerializer.attributeIntHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) { + attributeIntHex(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeIntHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeIntHexWithDefault( + name: String, + value: Int, + defaultValue: Int +) { + if (value != defaultValue) { + attributeIntHex(null, name, value) + } +} + +/** + * @see BinaryXmlSerializer.attributeLong + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) { + attributeLong(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeLong + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeLongWithDefault( + name: String, + value: Long, + defaultValue: Long +) { + if (value != defaultValue) { + attributeLong(null, name, value) + } +} + +/** + * @see BinaryXmlSerializer.attributeLongHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) { + attributeLongHex(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeLongHex + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeLongHexWithDefault( + name: String, + value: Long, + defaultValue: Long +) { + if (value != defaultValue) { + attributeLongHex(null, name, value) + } +} + +/** + * @see BinaryXmlSerializer.attributeFloat + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) { + attributeFloat(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeFloat + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeFloatWithDefault( + name: String, + value: Float, + defaultValue: Float +) { + if (value != defaultValue) { + attributeFloat(null, name, value) + } +} + +/** + * @see BinaryXmlSerializer.attributeDouble + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) { + attributeDouble(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeDouble + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeDoubleWithDefault( + name: String, + value: Double, + defaultValue: Double +) { + if (value != defaultValue) { + attributeDouble(null, name, value) + } +} + +/** + * @see BinaryXmlSerializer.attributeBoolean + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) { + attributeBoolean(null, name, value) +} + +/** + * @see BinaryXmlSerializer.attributeBoolean + */ +@Suppress("NOTHING_TO_INLINE") +@Throws(IOException::class) +inline fun BinaryXmlSerializer.attributeBooleanWithDefault( + name: String, + value: Boolean, + defaultValue: Boolean +) { + if (value != defaultValue) { + attributeBoolean(null, name, value) + } +} diff --git a/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt b/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt similarity index 69% rename from services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt rename to services/permission/java/com/android/server/permission/access/util/IntExtensions.kt index 21ec1593fdeb3..e71d7a129a9ef 100644 --- a/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt +++ b/services/permission/java/com/android/server/permission/access/util/IntExtensions.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -package com.android.server.permission +package com.android.server.permission.access.util -import com.android.internal.annotations.Keep -import com.android.server.pm.permission.PermissionManagerServiceInterface +fun Int.hasAnyBit(bits: Int): Boolean = this and bits != 0 -/** - * Modern implementation of [PermissionManagerServiceInterface]. - */ -@Keep -class ModernPermissionManagerServiceImpl +fun Int.hasBits(bits: Int): Boolean = this and bits == bits + +infix fun Int.andInv(other: Int): Int = this and other.inv() diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt new file mode 100644 index 0000000000000..e6b4e3e67b616 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.util + +import android.content.ApexEnvironment +import android.os.UserHandle +import java.io.File + +object PermissionApex { + private const val MODULE_NAME = "com.android.permission" + + /** + * @see ApexEnvironment.getDeviceProtectedDataDir + */ + val systemDataDirectory: File + get() = apexEnvironment.deviceProtectedDataDir + + /** + * @see ApexEnvironment.getDeviceProtectedDataDirForUser + */ + fun getUserDataDirectory(userId: Int): File = + apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId)) + + private val apexEnvironment: ApexEnvironment + get() = ApexEnvironment.getApexEnvironment(MODULE_NAME) +}