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

Commit c1861f63 authored by Hai Zhang's avatar Hai Zhang Committed by Android (Google) Code Review
Browse files

Merge "Move new unified permission subsystem into platform."

parents c725dfd2 2337c999
Loading
Loading
Loading
Loading
+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
        }
    }
}
+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"
    }
}
+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
        )
    }
}
+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
            }
        }
    }
}
+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