Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2337c999 authored by Hai Zhang's avatar Hai Zhang
Browse files

Move new unified permission subsystem into platform.

Bug: 182523293
Test: presubmit
Change-Id: Id96def0de91af70d840908cdbc449af5414c39bd
parent ad961027
Loading
Loading
Loading
Loading
+85 −0
Original line number 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
        }
    }
}
+114 −0
Original line number 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"
    }
}
+255 −0
Original line number 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
        )
    }
}
+126 −0
Original line number 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
            }
        }
    }
}
+83 −0
Original line number 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