Loading services/permission/java/com/android/server/permission/access/AccessCheckingService.kt 0 → 100644 +85 −0 Original line number Original line Diff line number Diff line /* * 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 } } } services/permission/java/com/android/server/permission/access/AccessPersistence.kt 0 → 100644 +114 −0 Original line number Original line Diff line number Diff line /* * 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 <T : WritableState> 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" } } services/permission/java/com/android/server/permission/access/AccessPolicy.kt 0 → 100644 +255 −0 Original line number Original line Diff line number Diff line /* * 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<String, IndexedMap<String, SchemePolicy>> ) { constructor() : this( IndexedMap<String, IndexedMap<String, SchemePolicy>>().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<OnDecisionChangedListener>() 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 ) } } services/permission/java/com/android/server/permission/access/AccessState.kt 0 → 100644 +126 −0 Original line number Original line Diff line number Diff line /* * 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<UserState> ) { constructor() : this(SystemState(), IntMap()) fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() }) } class SystemState private constructor( val userIds: IntSet, val packageStates: IndexedMap<String, PackageState>, val disabledSystemPackageStates: IndexedMap<String, PackageState>, val appIds: IntMap<IndexedListSet<String>>, // A map of KnownPackagesInt to a set of known package names val knownPackages: IntMap<IndexedListSet<String>>, // A map of userId to packageName val deviceAndProfileOwners: IntMap<String>, // A map of packageName to (A map of oem permission name to whether it's granted) val oemPermissions: IndexedMap<String, IndexedMap<String, Boolean>>, val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>, // A map of packageName to a set of vendor priv app permission names val vendorPrivAppPermissions: Map<String, Set<String>>, val productPrivAppPermissions: Map<String, Set<String>>, val systemExtPrivAppPermissions: Map<String, Set<String>>, val privAppPermissions: Map<String, Set<String>>, val apexPrivAppPermissions: Map<String, Map<String, Set<String>>>, val vendorPrivAppDenyPermissions: Map<String, Set<String>>, val productPrivAppDenyPermissions: Map<String, Set<String>>, val systemExtPrivAppDenyPermissions: Map<String, Set<String>>, val apexPrivAppDenyPermissions: Map<String, Map<String, Set<String>>>, val privAppDenyPermissions: Map<String, Set<String>>, val implicitToSourcePermissions: Map<String, Set<String>>, val permissionGroups: IndexedMap<String, PermissionGroupInfo>, val permissionTrees: IndexedMap<String, Permission>, val permissions: IndexedMap<String, Permission> ) : 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<IndexedMap<String, Int>>, val uidAppOpModes: IntMap<IndexedMap<String, Int>>, val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>> ) : 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 } } } } services/permission/java/com/android/server/permission/access/AccessUri.kt 0 → 100644 +83 −0 Original line number Original line Diff line number Diff line /* * 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" } } Loading
services/permission/java/com/android/server/permission/access/AccessCheckingService.kt 0 → 100644 +85 −0 Original line number Original line Diff line number Diff line /* * 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 } } }
services/permission/java/com/android/server/permission/access/AccessPersistence.kt 0 → 100644 +114 −0 Original line number Original line Diff line number Diff line /* * 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 <T : WritableState> 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" } }
services/permission/java/com/android/server/permission/access/AccessPolicy.kt 0 → 100644 +255 −0 Original line number Original line Diff line number Diff line /* * 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<String, IndexedMap<String, SchemePolicy>> ) { constructor() : this( IndexedMap<String, IndexedMap<String, SchemePolicy>>().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<OnDecisionChangedListener>() 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 ) } }
services/permission/java/com/android/server/permission/access/AccessState.kt 0 → 100644 +126 −0 Original line number Original line Diff line number Diff line /* * 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<UserState> ) { constructor() : this(SystemState(), IntMap()) fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() }) } class SystemState private constructor( val userIds: IntSet, val packageStates: IndexedMap<String, PackageState>, val disabledSystemPackageStates: IndexedMap<String, PackageState>, val appIds: IntMap<IndexedListSet<String>>, // A map of KnownPackagesInt to a set of known package names val knownPackages: IntMap<IndexedListSet<String>>, // A map of userId to packageName val deviceAndProfileOwners: IntMap<String>, // A map of packageName to (A map of oem permission name to whether it's granted) val oemPermissions: IndexedMap<String, IndexedMap<String, Boolean>>, val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>, // A map of packageName to a set of vendor priv app permission names val vendorPrivAppPermissions: Map<String, Set<String>>, val productPrivAppPermissions: Map<String, Set<String>>, val systemExtPrivAppPermissions: Map<String, Set<String>>, val privAppPermissions: Map<String, Set<String>>, val apexPrivAppPermissions: Map<String, Map<String, Set<String>>>, val vendorPrivAppDenyPermissions: Map<String, Set<String>>, val productPrivAppDenyPermissions: Map<String, Set<String>>, val systemExtPrivAppDenyPermissions: Map<String, Set<String>>, val apexPrivAppDenyPermissions: Map<String, Map<String, Set<String>>>, val privAppDenyPermissions: Map<String, Set<String>>, val implicitToSourcePermissions: Map<String, Set<String>>, val permissionGroups: IndexedMap<String, PermissionGroupInfo>, val permissionTrees: IndexedMap<String, Permission>, val permissions: IndexedMap<String, Permission> ) : 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<IndexedMap<String, Int>>, val uidAppOpModes: IntMap<IndexedMap<String, Int>>, val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>> ) : 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 } } } }
services/permission/java/com/android/server/permission/access/AccessUri.kt 0 → 100644 +83 −0 Original line number Original line Diff line number Diff line /* * 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" } }