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

Commit 4ff9f6d4 authored by Hai Zhang's avatar Hai Zhang
Browse files

Optimize check for requested permissions.

Added requestedPackageStates in evalutePermissionState() so that
requested permissions is only checked for one time.

any/forEachRequestingPackageInAppId() is removed in favor of
any/forEachPackageInAppId() - other usages outside of
evaluatePermissionState() can manually check for requested permissions
if needed, and this also benefits performance a bit in that other faster
checks can be done before checking requested permissions.

Also changed revokePermissionsOnPackageUpdate() to check for all packages
in the app ID, because storage isolation always applies to the entire
UID regardless of whether they requested a certain permission, and
this is also more consistent with the old subsystem.

The added reduce*() extension functions are hard coded to work with
Int instead of using generics, because generics will compile into
boxed Integer in bytecode despite that the functions are inlined.

Bug: 278913322
Test: presubmit
Change-Id: I6cbace38b5ea7d1782da3b1c1ee68683073080ba
parent d9041331
Loading
Loading
Loading
Loading
+12 −0
Original line number Original line Diff line number Diff line
@@ -64,6 +64,18 @@ inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boole
operator fun <T> IndexedList<T>.plus(element: T): MutableIndexedList<T> =
operator fun <T> IndexedList<T>.plus(element: T): MutableIndexedList<T> =
    toMutable().apply { this += element }
    toMutable().apply { this += element }


// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
inline fun <T> IndexedList<T>.reduceIndexed(
    initialValue: Int,
    accumulator: (Int, Int, T) -> Int
): Int {
    var value = initialValue
    forEachIndexed { index, element ->
        value = accumulator(value, index, element)
    }
    return value
}

@Suppress("NOTHING_TO_INLINE")
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MutableIndexedList<T>.minusAssign(element: T) {
inline operator fun <T> MutableIndexedList<T>.minusAssign(element: T) {
    remove(element)
    remove(element)
+12 −0
Original line number Original line Diff line number Diff line
@@ -64,6 +64,18 @@ inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Bo
operator fun <T> IndexedListSet<T>.plus(element: T): MutableIndexedListSet<T> =
operator fun <T> IndexedListSet<T>.plus(element: T): MutableIndexedListSet<T> =
    toMutable().apply { this += element }
    toMutable().apply { this += element }


// Using Int instead of <R> to avoid autoboxing, since we only have the use case for Int.
inline fun <T> IndexedListSet<T>.reduceIndexed(
    initialValue: Int,
    accumulator: (Int, Int, T) -> Int
): Int {
    var value = initialValue
    forEachIndexed { index, element ->
        value = accumulator(value, index, element)
    }
    return value
}

@Suppress("NOTHING_TO_INLINE")
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> MutableIndexedListSet<T>.minusAssign(element: T) {
inline operator fun <T> MutableIndexedListSet<T>.minusAssign(element: T) {
    remove(element)
    remove(element)
+95 −82
Original line number Original line Diff line number Diff line
@@ -205,8 +205,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
            if (!permission.isHardOrSoftRestricted) {
            if (!permission.isHardOrSoftRestricted) {
                return@forEach
                return@forEach
            }
            }
            val isRequestedBySystemPackage =
            val isRequestedBySystemPackage = anyPackageInAppId(appId) {
                anyRequestingPackageInAppId(appId, permissionName) { it.isSystem }
                it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
            }
            if (isRequestedBySystemPackage) {
            if (isRequestedBySystemPackage) {
                return@forEach
                return@forEach
            }
            }
@@ -247,8 +248,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
            if (permission.isRemoved) {
            if (permission.isRemoved) {
                return@forEach
                return@forEach
            }
            }
            val isRequestedByOtherPackages = anyRequestingPackageInAppId(appId, permissionName) {
            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
                it.packageName != packageName
                it.packageName != packageName &&
                    permissionName in it.androidPackage!!.requestedPermissions
            }
            }
            if (isRequestedByOtherPackages) {
            if (isRequestedByOtherPackages) {
                return@forEach
                return@forEach
@@ -630,32 +632,45 @@ class AppIdPermissionPolicy : SchemePolicy() {
            // information about the package before OTA anyway.
            // information about the package before OTA anyway.
            return
            return
        }
        }

        // If the app is updated, and has scoped storage permissions, then it is possible that the
        // If the app is updated, and has scoped storage permissions, then it is possible that the
        // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
        // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
        newState.userStates.forEachIndexed { _, userId, userState ->
        val oldTargetSdkVersion =
            userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
            reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) {
                _, permissionName, oldFlags ->
                targetSdkVersion, packageState ->
                if (permissionName !in STORAGE_AND_MEDIA_PERMISSIONS || oldFlags == 0) {
                targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
                    return@forEachReversedIndexed
            }
            }
                val oldTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, oldState)
        val newTargetSdkVersion =
                val newTargetSdkVersion = getAppIdTargetSdkVersion(appId, permissionName, newState)
            reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) {
                targetSdkVersion, packageState ->
                targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
            }
        @Suppress("ConvertTwoComparisonsToRangeCheck")
        val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
        val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q &&
            newTargetSdkVersion < Build.VERSION_CODES.Q
            newTargetSdkVersion < Build.VERSION_CODES.Q
        @Suppress("ConvertTwoComparisonsToRangeCheck")
        val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
        val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q &&
            newTargetSdkVersion >= Build.VERSION_CODES.Q
            newTargetSdkVersion >= Build.VERSION_CODES.Q
                val oldIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
        val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) {
                    appId, permissionName, oldState
            it.androidPackage!!.isRequestLegacyExternalStorage
                ) { it.androidPackage!!.isRequestLegacyExternalStorage }
        }
                val newIsRequestLegacyExternalStorage = anyRequestingPackageInAppId(
        val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) {
                    appId, permissionName, newState
            it.androidPackage!!.isRequestLegacyExternalStorage
                ) { it.androidPackage!!.isRequestLegacyExternalStorage }
        }
        val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
        val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded &&
            !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
            !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage
                if ((isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded) &&
        val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage ||
            isTargetSdkVersionDowngraded
        if (shouldRevokeStorageAndMediaPermissions) {
            newState.userStates.forEachIndexed { _, userId, userState ->
                userState.appIdPermissionFlags[appId]?.forEachReversedIndexed {
                    _, permissionName, oldFlags ->
                    if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS &&
                        oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
                        oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED)) {
                    Slog.v(LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
                        Slog.v(
                            " $appId and user: $userId")
                            LOG_TAG, "Revoking storage permission: $permissionName for appId: " +
                                " $appId and user: $userId"
                        )
                        val newFlags = oldFlags andInv (
                        val newFlags = oldFlags andInv (
                            PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
                            PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK
                        )
                        )
@@ -664,6 +679,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
                }
                }
            }
            }
        }
        }
    }


    private fun MutateStateScope.evaluatePermissionStateForAllPackages(
    private fun MutateStateScope.evaluatePermissionStateForAllPackages(
        permissionName: String,
        permissionName: String,
@@ -672,8 +688,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
        val externalState = newState.externalState
        val externalState = newState.externalState
        externalState.userIds.forEachIndexed { _, userId ->
        externalState.userIds.forEachIndexed { _, userId ->
            externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
            externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
                val isPermissionRequested =
                val isPermissionRequested = anyPackageInAppId(appId) {
                    anyRequestingPackageInAppId(appId, permissionName) { true }
                    permissionName in it.androidPackage!!.requestedPermissions
                }
                if (isPermissionRequested) {
                if (isPermissionRequested) {
                    evaluatePermissionState(appId, userId, permissionName, installedPackageState)
                    evaluatePermissionState(appId, userId, permissionName, installedPackageState)
                }
                }
@@ -711,8 +728,21 @@ class AppIdPermissionPolicy : SchemePolicy() {
        installedPackageState: PackageState?
        installedPackageState: PackageState?
    ) {
    ) {
        val packageNames = newState.externalState.appIdPackageNames[appId]!!
        val packageNames = newState.externalState.appIdPackageNames[appId]!!
        val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
        // Repeatedly checking whether a permission is requested can actually be costly, so we cache
            newState.externalState.packageStates[packageName]!!.androidPackage == null
        // the result for this method which is frequently called during boot, instead of calling
        // anyPackageInAppId() and checking requested permissions multiple times.
        val requestingPackageStates = MutableIndexedList<PackageState>()
        var hasMissingPackage = false
        packageNames.forEachIndexed { _, packageName ->
            val packageState = newState.externalState.packageStates[packageName]!!
            val androidPackage = packageState.androidPackage
            if (androidPackage != null) {
                if (permissionName in androidPackage.requestedPermissions) {
                    requestingPackageStates += packageState
                }
            } else {
                hasMissingPackage = true
            }
        }
        }
        if (packageNames.size == 1 && hasMissingPackage) {
        if (packageNames.size == 1 && hasMissingPackage) {
            // For non-shared-user packages with missing androidPackage, skip evaluation.
            // For non-shared-user packages with missing androidPackage, skip evaluation.
@@ -736,8 +766,8 @@ class AppIdPermissionPolicy : SchemePolicy() {
                val isRequestedByInstalledPackage = installedPackageState != null &&
                val isRequestedByInstalledPackage = installedPackageState != null &&
                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
                    permissionName in installedPackageState.androidPackage!!.requestedPermissions
                val isRequestedBySystemPackage =
                val isRequestedBySystemPackage =
                    anyRequestingPackageInAppId(appId, permissionName) { it.isSystem }
                    requestingPackageStates.anyIndexed { _, it -> it.isSystem }
                val isCompatibilityPermission = anyRequestingPackageInAppId(appId, permissionName) {
                val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it ->
                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
                    isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
                }
                }
                // If this is an existing, non-system package,
                // If this is an existing, non-system package,
@@ -762,18 +792,15 @@ class AppIdPermissionPolicy : SchemePolicy() {
                // Keep the non-runtime permission grants for shared UID with missing androidPackage
                // Keep the non-runtime permission grants for shared UID with missing androidPackage
                PermissionFlags.PROTECTION_GRANTED
                PermissionFlags.PROTECTION_GRANTED
            } else {
            } else {
                val mayGrantByPrivileged = !permission.isPrivileged || (
                val mayGrantByPrivileged = !permission.isPrivileged ||
                    anyRequestingPackageInAppId(appId, permissionName) {
                    requestingPackageStates.anyIndexed { _, it ->
                        checkPrivilegedPermissionAllowlist(it, permission)
                        checkPrivilegedPermissionAllowlist(it, permission)
                    }
                    }
                )
                val shouldGrantBySignature = permission.isSignature &&
                val shouldGrantBySignature = permission.isSignature && (
                    requestingPackageStates.anyIndexed { _, it ->
                    anyRequestingPackageInAppId(appId, permissionName) {
                        shouldGrantPermissionBySignature(it, permission)
                        shouldGrantPermissionBySignature(it, permission)
                    }
                    }
                )
                val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it ->
                val shouldGrantByProtectionFlags =
                    anyRequestingPackageInAppId(appId, permissionName) {
                    shouldGrantPermissionByProtectionFlags(it, permission)
                    shouldGrantPermissionByProtectionFlags(it, permission)
                }
                }
                if (mayGrantByPrivileged &&
                if (mayGrantByPrivileged &&
@@ -805,7 +832,12 @@ class AppIdPermissionPolicy : SchemePolicy() {
        } else if (permission.isRuntime) {
        } else if (permission.isRuntime) {
            var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
            var newFlags = oldFlags and PermissionFlags.MASK_RUNTIME
            val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags)
            val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags)
            if (getAppIdTargetSdkVersion(appId, permissionName) < Build.VERSION_CODES.M) {
            val targetSdkVersion =
                requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) {
                    targetSdkVersion, _, packageState ->
                    targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion)
                }
            if (targetSdkVersion < Build.VERSION_CODES.M) {
                if (permission.isRuntimeOnly) {
                if (permission.isRuntimeOnly) {
                    // Different from the old implementation, which simply skips a runtime-only
                    // Different from the old implementation, which simply skips a runtime-only
                    // permission, we now only allow holding on to the restriction related flags,
                    // permission, we now only allow holding on to the restriction related flags,
@@ -837,7 +869,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
                val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
                val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED)
                val isLeanbackNotificationsPermission = newState.externalState.isLeanback &&
                val isLeanbackNotificationsPermission = newState.externalState.isLeanback &&
                    permissionName in NOTIFICATIONS_PERMISSIONS
                    permissionName in NOTIFICATIONS_PERMISSIONS
                val isImplicitPermission = anyRequestingPackageInAppId(appId, permissionName) {
                val isImplicitPermission = requestingPackageStates.anyIndexed { _, it ->
                    permissionName in it.androidPackage!!.implicitPermissions
                    permissionName in it.androidPackage!!.implicitPermissions
                }
                }
                val sourcePermissions = newState.externalState
                val sourcePermissions = newState.externalState
@@ -1108,18 +1140,6 @@ class AppIdPermissionPolicy : SchemePolicy() {
        }
        }
    }
    }


    private fun MutateStateScope.getAppIdTargetSdkVersion(
        appId: Int,
        permissionName: String,
        state: AccessState = newState
    ): Int {
        var targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT
        forEachRequestingPackageInAppId(appId, permissionName, state) {
            targetSdkVersion = targetSdkVersion.coerceAtMost(it.androidPackage!!.targetSdkVersion)
        }
        return targetSdkVersion
    }

    private inline fun MutateStateScope.anyPackageInAppId(
    private inline fun MutateStateScope.anyPackageInAppId(
        appId: Int,
        appId: Int,
        state: AccessState = newState,
        state: AccessState = newState,
@@ -1128,22 +1148,10 @@ class AppIdPermissionPolicy : SchemePolicy() {
        val packageNames = state.externalState.appIdPackageNames[appId]!!
        val packageNames = state.externalState.appIdPackageNames[appId]!!
        return packageNames.anyIndexed { _, packageName ->
        return packageNames.anyIndexed { _, packageName ->
            val packageState = state.externalState.packageStates[packageName]!!
            val packageState = state.externalState.packageStates[packageName]!!
            val androidPackage = packageState.androidPackage
            packageState.androidPackage != null && predicate(packageState)
            androidPackage != null && predicate(packageState)
        }
        }
    }
    }


    private inline fun MutateStateScope.anyRequestingPackageInAppId(
        appId: Int,
        permissionName: String,
        state: AccessState = newState,
        predicate: (PackageState) -> Boolean
    ): Boolean =
        anyPackageInAppId(appId, state) { packageState ->
            permissionName in packageState.androidPackage!!.requestedPermissions &&
                predicate(packageState)
        }

    private inline fun MutateStateScope.forEachPackageInAppId(
    private inline fun MutateStateScope.forEachPackageInAppId(
        appId: Int,
        appId: Int,
        state: AccessState = newState,
        state: AccessState = newState,
@@ -1158,15 +1166,20 @@ class AppIdPermissionPolicy : SchemePolicy() {
        }
        }
    }
    }


    private inline fun MutateStateScope.forEachRequestingPackageInAppId(
    // Using Int instead of <T> to avoid autoboxing, since we only have the use case for Int.
    private inline fun MutateStateScope.reducePackageInAppId(
        appId: Int,
        appId: Int,
        permissionName: String,
        initialValue: Int,
        state: AccessState = newState,
        state: AccessState = newState,
        action: (PackageState) -> Unit
        accumulator: (Int, PackageState) -> Int
    ) {
    ): Int {
        forEachPackageInAppId(appId, state) { packageState ->
        val packageNames = state.externalState.appIdPackageNames[appId]!!
            if (permissionName in packageState.androidPackage!!.requestedPermissions) {
        return packageNames.reduceIndexed(initialValue) { value, _, packageName ->
                action(packageState)
            val packageState = state.externalState.packageStates[packageName]!!
            if (packageState.androidPackage != null) {
                accumulator(value, packageState)
            } else {
                value
            }
            }
        }
        }
    }
    }