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

Commit 9de78dea authored by Akhil Gangu's avatar Akhil Gangu
Browse files

Update install permissions policy to handle app compat purpose

declarations.

1. Purpose validation should only apply if the app's targetSdkVersion is greater than or equal to the requiresPurposeTargetSdkVersion set on the permission. This will ensure pre-existing permissions changed to require purpose will be granted gracefully to handle app compat cases.
2. A declared purpose is only valid if the app's targetSdkVersion is less than or equal to the purposes's maxTargetSdkVersion. This ensures a deprecated purpose can continue to be used for versions <= maxTargetSdkVersion while requiring a new valid purpose for versions above it.

Bug: 412443420
Bug: 422817717
Test: atest AppIdPermissionPolicyTest
Flag: android.permission.flags.purpose_declaration_enabled
Change-Id: I6a05ba2bf6c842b720720be291aaf847743e8d0f
parent a8577c51
Loading
Loading
Loading
Loading
+10 −8
Original line number Diff line number Diff line
@@ -1321,20 +1321,22 @@ class AppIdPermissionPolicy : SchemePolicy() {
        return false
    }

    // TODO(b/422817717) - Handle app-compat scenarios by using versioning info at the purpose level
    private fun hasValidPurposeForPackage(
        androidPackage: AndroidPackage,
        permission: Permission,
    ): Boolean {
        // TODO(b/419394842) - Use < Android C build version code when available.
        if (androidPackage.targetSdkVersion <= Build.VERSION_CODES.BAKLAVA) {
            // Purpose declaration is not supported on older target versions. Bypass check.
        val targetSdkVersion = androidPackage.targetSdkVersion
        if (targetSdkVersion < permission.requiresPurposeTargetSdkVersion) {
            return true
        }
        val purposes = androidPackage.usesPermissionMapping[permission.name]?.purposes
        // There must be at least one valid purpose defined as the parsing logic would otherwise
        // force set the permission to not require purpose, making this code unreachable.
        return purposes?.any { it in permission.validPurposes } ?: false
        val purposes =
            androidPackage.usesPermissionMapping[permission.name]?.purposes ?: return false
        return purposes.any {
            // NOTE: Map cannot be empty. The package parser ensures at least one valid purpose is
            // required to be defined when {@code requiresPurpose} is {@code true}.
            val validPurpose = permission.validPurposes[it]
            validPurpose != null && targetSdkVersion <= validPurpose.maxTargetSdkVersion
        }
    }

    private fun MutateStateScope.shouldGrantPermissionBySignature(
+5 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.permission.access.permission

import android.content.pm.PermissionInfo
import android.content.pm.ValidPurposeInfo
import android.os.UserHandle
import com.android.server.permission.access.util.hasBits
import libcore.util.EmptyArray
@@ -149,7 +150,10 @@ data class Permission(
    inline val requiresPurpose: Boolean
        get() = permissionInfo.requiresPurpose

    inline val validPurposes: Set<String>
    inline val requiresPurposeTargetSdkVersion: Int
        get() = permissionInfo.requiresPurposeTargetSdkVersion

    inline val validPurposes: Map<String, ValidPurposeInfo>
        get() = permissionInfo.validPurposes

    inline val hasGids: Boolean
+110 −32
Original line number Diff line number Diff line
@@ -71,10 +71,7 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
    @Test
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithNoPurpose_permissionRevoked() {
        testOnPackageAddedForPurposeDeclaration(
            BUILD_VERSION_CODES_C,
            emptySet()
        )
        testOnPackageAddedForPurposeDeclaration()

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PURPOSE_REVOKED
@@ -93,10 +90,7 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
    @Test
    @DisableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithNoPurposeAndFlagDisabled_permissionGranted() {
        testOnPackageAddedForPurposeDeclaration(
            BUILD_VERSION_CODES_C,
            emptySet()
        )
        testOnPackageAddedForPurposeDeclaration()

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
@@ -115,10 +109,7 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
    @Test
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithInvalidPurpose_permissionRevoked() {
        testOnPackageAddedForPurposeDeclaration(
            BUILD_VERSION_CODES_C,
            setOf(INVALID_PURPOSE)
        )
        testOnPackageAddedForPurposeDeclaration(purposes = setOf(INVALID_PURPOSE))

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PURPOSE_REVOKED
@@ -138,9 +129,8 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithValidAndInvalidPurpose_permissionGranted() {
        testOnPackageAddedForPurposeDeclaration(
            BUILD_VERSION_CODES_C,
            // At least one valid purpose is sufficient for the permission to be granted.
            setOf(VALID_PURPOSE, INVALID_PURPOSE),
            purposes = setOf(VALID_PURPOSE_0, INVALID_PURPOSE),
            oldFlags = PermissionFlags.INSTALL_REVOKED or PermissionFlags.PURPOSE_REVOKED
        )

@@ -162,8 +152,7 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithValidPurpose_permissionGranted() {
        testOnPackageAddedForPurposeDeclaration(
            BUILD_VERSION_CODES_C,
            setOf(VALID_PURPOSE),
            purposes = setOf(VALID_PURPOSE_0),
            oldFlags = PermissionFlags.INSTALL_REVOKED or PermissionFlags.PURPOSE_REVOKED
        )

@@ -184,10 +173,7 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
    @Test
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithNoPurposeTargetingOldSdk_permissionGranted() {
        testOnPackageAddedForPurposeDeclaration(
            Build.VERSION_CODES.BAKLAVA,
            emptySet()
        )
        testOnPackageAddedForPurposeDeclaration(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
@@ -220,17 +206,14 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
                            PERMISSION_NAME_0 to
                                mockParsedUsesPermission(
                                    PERMISSION_NAME_0,
                                    purposes = setOf(VALID_PURPOSE)
                                    purposes = setOf(VALID_PURPOSE_0)
                                )
                        )
                )
            )
        addPackageState(sharedUidPackage1)

        testOnPackageAddedForPurposeDeclaration(
            BUILD_VERSION_CODES_C,
            emptySet()
        )
        testOnPackageAddedForPurposeDeclaration()

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
@@ -263,10 +246,7 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
            )
        addPackageState(sharedUidPackage1)

        testOnPackageAddedForPurposeDeclaration(
            BUILD_VERSION_CODES_C,
            emptySet()
        )
        testOnPackageAddedForPurposeDeclaration()

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED
@@ -283,9 +263,96 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
        assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithDeprecatedPurposeOnLastValidSdk_permissionGranted() {
        testOnPackageAddedForPurposeDeclaration(purposes = setOf(VALID_PURPOSE_1))

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED

        assertWithMessage(
            "After onPackageAdded() is called for appId $APP_ID_0 that requests a permission," +
                " which requires purpose, with a deprecated purpose whose maxTargetSdkVersion" +
                " matches the app's targetSdkVersion, permission flags ($actualFlags) should be" +
                " install granted ($expectedNewFlags)."
        )
            .that(actualFlags)
            .isEqualTo(expectedNewFlags)

        assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithTargetSdkLowerThanRequiresPurposeSdk_permissionGranted() {
        testOnPackageAddedForPurposeDeclaration(
            requiresPurposeTargetSdkVersion = BUILD_VERSION_CODES_D
        )

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED

        assertWithMessage(
            "After onPackageAdded() is called for appId $APP_ID_0 that requests a permission," +
                " which requires purpose, with no purpose but the app's targetSdkVersion is lower" +
                " than the permission's requiresPurposeTargetSdkVersion, permission flags" +
                " ($actualFlags) should be install granted ($expectedNewFlags)."
        )
            .that(actualFlags)
            .isEqualTo(expectedNewFlags)

        assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue()
    }

    @Test
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithDeprecatedPurpose_permissionRevoked() {
        testOnPackageAddedForPurposeDeclaration(
            purposes = setOf(VALID_PURPOSE_1), // This purpose is deprecated as of Android C.
            targetSdkVersion = BUILD_VERSION_CODES_D
        )

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PURPOSE_REVOKED

        assertWithMessage(
            "After onPackageAdded() is called for appId $APP_ID_0 that requests a permission," +
                " which requires purpose, with a deprecated purpose, the actual permission flags" +
                " ($actualFlags) should be install granted + purpose revoked ($expectedNewFlags)."
        )
            .that(actualFlags)
            .isEqualTo(expectedNewFlags)

        assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isFalse()
    }

    @Test
    @EnableFlags(Flags.FLAG_PURPOSE_DECLARATION_ENABLED)
    fun testOnPackageAdded_requestsPermissionWithDeprecatedAndValidPurpose_permissionGranted() {
        testOnPackageAddedForPurposeDeclaration(
            purposes = setOf(VALID_PURPOSE_1, VALID_PURPOSE_0),
            targetSdkVersion = BUILD_VERSION_CODES_D,
        )

        val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0)
        val expectedNewFlags = PermissionFlags.INSTALL_GRANTED

        assertWithMessage(
            "After onPackageAdded() is called for appId $APP_ID_0 that requests a permission," +
                " which requires purpose, with a deprecated and active (valid) purpose, the" +
                " permission flags ($actualFlags) should be install granted ($expectedNewFlags)."
        )
            .that(actualFlags)
            .isEqualTo(expectedNewFlags)

        assertThat(PermissionFlags.isPermissionGranted(actualFlags)).isTrue()
    }

    private fun testOnPackageAddedForPurposeDeclaration(
        targetSdkVersion: Int,
        purposes: Set<String>,
        purposes: Set<String> = emptySet(),
        targetSdkVersion: Int = BUILD_VERSION_CODES_C,
        requiresPurposeTargetSdkVersion: Int = BUILD_VERSION_CODES_C,
        oldFlags: Int = PermissionFlags.INSTALL_GRANTED,
    ) {
        val addedPackage =
@@ -302,10 +369,21 @@ class AppIdPermissionPolicyTest : BasePermissionPolicyTest() {
                        )
                )
            )
        val permission = mockParsedPermission(
            PERMISSION_NAME_0,
            PLATFORM_PACKAGE_NAME,
            isPurposeRequired = true,
            requiresPurposeTargetSdkVersion = requiresPurposeTargetSdkVersion,
            validPurposes =
                listOf(
                    mockParsedValidPurpose(VALID_PURPOSE_0),
                    mockParsedValidPurpose(VALID_PURPOSE_1, requiresPurposeTargetSdkVersion)
                )
        )
        val platformPackage = mockPackageState(PLATFORM_APP_ID, mockAndroidPackage(PLATFORM_PACKAGE_NAME))
        addPackageState(platformPackage)
        addPackageState(addedPackage)
        addPermission(defaultPermissionWithValidPurpose)
        addPermission(permission)
        setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, oldFlags)

        mutateState {
+23 −6
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.pm.PackageManager
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.content.pm.SigningDetails
import android.content.pm.ValidPurposeInfo
import android.health.connect.HealthPermissions
import android.os.Build
import android.os.Bundle
@@ -31,6 +32,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.pm.pkg.component.ParsedPermission
import com.android.internal.pm.pkg.component.ParsedPermissionGroup
import com.android.internal.pm.pkg.component.ParsedUsesPermission
import com.android.internal.pm.pkg.component.ParsedValidPurpose
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.permission.access.MutableAccessState
@@ -63,8 +65,6 @@ abstract class BasePermissionPolicyTest {
    protected val defaultPermissionTree =
        mockParsedPermission(PERMISSION_TREE_NAME, PACKAGE_NAME_0, isTree = true)
    protected val defaultPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0)
    protected val defaultPermissionWithValidPurpose = mockParsedPermission(PERMISSION_NAME_0,
            PLATFORM_PACKAGE_NAME, isPurposeRequired = true, validPurposes = setOf(VALID_PURPOSE))

    protected val appIdPermissionPolicy = AppIdPermissionPolicy()

@@ -116,7 +116,12 @@ abstract class BasePermissionPolicyTest {
                    group = parsedPermission.group
                    flags = parsedPermission.flags
                    requiresPurpose = parsedPermission.isPurposeRequired
                    validPurposes = parsedPermission.validPurposes
                    requiresPurposeTargetSdkVersion =
                        parsedPermission.requiresPurposeTargetSdkVersion
                    validPurposes =
                        parsedPermission.validPurposes.associate { it ->
                            it.name to ValidPurposeInfo(it.name, it.maxTargetSdkVersion)
                        }
                }
            }
    }
@@ -250,7 +255,8 @@ abstract class BasePermissionPolicyTest {
        flags: Int = 0,
        isTree: Boolean = false,
        isPurposeRequired: Boolean = false,
        validPurposes: Set<String> = emptySet(),
        requiresPurposeTargetSdkVersion: Int = BUILD_VERSION_CODES_C,
        validPurposes: List<ParsedValidPurpose> = emptyList(),
    ): ParsedPermission = mock {
        whenever(name).thenReturn(permissionName)
        whenever(this.packageName).thenReturn(packageName)
@@ -261,6 +267,7 @@ abstract class BasePermissionPolicyTest {
        whenever(this.flags).thenReturn(flags)
        whenever(this.isTree).thenReturn(isTree)
        whenever(this.isPurposeRequired).thenReturn(isPurposeRequired)
        whenever(this.requiresPurposeTargetSdkVersion).thenReturn(requiresPurposeTargetSdkVersion)
        whenever(this.validPurposes).thenReturn(validPurposes)
    }

@@ -274,6 +281,14 @@ abstract class BasePermissionPolicyTest {
        whenever(this.purposes).thenReturn(purposes)
    }

    protected fun mockParsedValidPurpose(
        purposeName: String,
        maxTargetSdkVersion: Int = Int.MAX_VALUE,
    ): ParsedValidPurpose = mock {
        whenever(name).thenReturn(purposeName)
        whenever(this.maxTargetSdkVersion).thenReturn(maxTargetSdkVersion)
    }

    protected fun mockParsedPermissionGroup(
        permissionGroupName: String,
        packageName: String,
@@ -402,7 +417,8 @@ abstract class BasePermissionPolicyTest {

        @JvmStatic protected val PERMISSION_TREE_NAME = "permissionTree"

        @JvmStatic protected val VALID_PURPOSE = "validPurpose"
        @JvmStatic protected val VALID_PURPOSE_0 = "validPurpose0"
        @JvmStatic protected val VALID_PURPOSE_1 = "validPurpose1"
        @JvmStatic protected val INVALID_PURPOSE = "invalidPurpose"

        @JvmStatic protected val PERMISSION_NAME_0 = "permissionName0"
@@ -432,7 +448,8 @@ abstract class BasePermissionPolicyTest {
        @JvmStatic protected val USER_ID_0 = 0
        @JvmStatic protected val USER_ID_NEW = 1

        // TODO(b/419394842) - Remove and use Android C build version code directly when available.
        // TODO(b/419394842) - Remove and use defined build version codes directly when available.
        @JvmStatic protected val BUILD_VERSION_CODES_C = Build.VERSION_CODES.BAKLAVA + 1
        @JvmStatic protected val BUILD_VERSION_CODES_D = Build.VERSION_CODES.BAKLAVA + 2
    }
}