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

Commit 37b2836d authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Restrict callers for setSupervisionEnabledForUser" into main

parents ac72974c 8cd62fdd
Loading
Loading
Loading
Loading
+25 −4
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.supervision;

import static android.Manifest.permission.BYPASS_ROLE_QUALIFICATION;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_ROLE_HOLDERS;
import static android.Manifest.permission.MANAGE_USERS;
@@ -53,6 +54,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -138,7 +140,7 @@ public class SupervisionService extends ISupervisionManager.Stub {

    @Override
    public void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
        // TODO(b/395630828): Ensure that this method can only be called by the system.
        enforceCallerCanSetSupervisionEnabled();
        if (UserHandle.getUserId(Binder.getCallingUid()) != userId) {
            enforcePermission(INTERACT_ACROSS_USERS);
        }
@@ -577,21 +579,40 @@ public class SupervisionService extends ISupervisionManager.Stub {

    /** Enforces that the caller has the given permission. */
    private void enforcePermission(String permission) {
        checkCallAuthorization(
                mInjector.context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED);
        checkCallAuthorization(hasCallingPermission(permission));
    }

    /** Enforces that the caller has at least one of the given permission. */
    private void enforceAnyPermission(String... permissions) {
        boolean authorized = false;
        for (String permission : permissions) {
            if (mInjector.context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
            if (hasCallingPermission(permission)) {
                authorized = true;
                break;
            }
        }
        checkCallAuthorization(authorized);
    }

    /**
     * Enforces that the caller can set supervision enabled state.
     *
     * This is restricted to the callers with the root, shell, or system uid or callers with the
     * BYPASS_ROLE_QUALIFICATION permission. This permission is only granted to the
     * SYSTEM_SHELL role holder.
     */
    private void enforceCallerCanSetSupervisionEnabled() {
        checkCallAuthorization(isCallerSystem() || hasCallingPermission(BYPASS_ROLE_QUALIFICATION));
    }

    private boolean hasCallingPermission(String permission) {
        return mInjector.context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
    }

    private boolean isCallerSystem() {
        return UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID);
    }

    /** Provides local services in a lazy manner. */
    static class Injector {
        public Context context;
+55 −19
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.supervision

import android.Manifest.permission.BYPASS_ROLE_QUALIFICATION
import android.app.Activity
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
@@ -32,6 +33,8 @@ import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_DENIED
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.content.pm.PackageManagerInternal
import android.content.pm.UserInfo
import android.content.pm.UserInfo.FLAG_FOR_TESTING
@@ -61,6 +64,7 @@ import com.android.server.supervision.SupervisionService.ACTION_CONFIRM_SUPERVIS
import com.android.server.supervision.SupervisionService.RoleManagerWrapper
import com.google.common.truth.Truth.assertThat
import java.nio.file.Files
import kotlin.test.assertFailsWith
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -95,14 +99,16 @@ class SupervisionServiceTest {
    @Mock private lateinit var mockUserManagerInternal: UserManagerInternal
    @Mock private lateinit var mockRoleManager: SupervisionService.RoleManagerWrapper

    private lateinit var context: Context
    private lateinit var context: SupervisionContextWrapper
    private lateinit var lifecycle: SupervisionService.Lifecycle
    private lateinit var service: SupervisionService

    @Before
    fun setUp() {
        context = InstrumentationRegistry.getInstrumentation().context
        context = SupervisionContextWrapper(context, mockKeyguardManager, mockPackageManager,
        context = SupervisionContextWrapper(
            InstrumentationRegistry.getInstrumentation().context,
            mockKeyguardManager,
            mockPackageManager,
            mockRoleManager)

        LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
@@ -118,6 +124,11 @@ class SupervisionServiceTest {
        SupervisionSettings.getInstance()
            .changeDirForTesting(Files.createTempDirectory("tempSupervisionFolder").toFile())

        // Simulate that this test has the BYPASS_ROLE_QUALIFICATION permission. This is needed to
        // bypass the system uid check in setSupervisionEnabled. This is the permission the
        // supervision CTS tests use to enable supervision.
        context.permissions[BYPASS_ROLE_QUALIFICATION] = PERMISSION_GRANTED

        service = SupervisionService(context)
        lifecycle = SupervisionService.Lifecycle(context, service)
        lifecycle.registerProfileOwnerListener()
@@ -279,12 +290,20 @@ class SupervisionServiceTest {
        assertThat(getSecureSetting(SEARCH_CONTENT_FILTERS_ENABLED)).isEqualTo(-1)
    }

    @Test
    fun setSupervisionEnabledForUser_noPermission_throwsException() {
        context.permissions[BYPASS_ROLE_QUALIFICATION] = PERMISSION_DENIED
        assertFailsWith<SecurityException> {
            service.setSupervisionEnabledForUser(USER_ID, false)
        }
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_REMOVE_POLICIES_ON_SUPERVISION_DISABLE)
    fun setSupervisionEnabledForUser_removesPoliciesWhenDisabling() {
        for ((role, packageName) in supervisionRoleHolders) {
            whenever(mockRoleManager.getRoleHoldersAsUser(eq(role), any()))
                .thenReturn(listOf(packageName));
                .thenReturn(listOf(packageName))
        }

        service.setSupervisionEnabledForUser(USER_ID, false)
@@ -511,7 +530,7 @@ class SupervisionServiceTest {
    private fun setSupervisionEnabledForUser(
        expectedUserId: Int,
        enabled: Boolean,
        listeners: Map<Int, Pair<ISupervisionListener, IBinder>>,
        listeners: Map<Int, Pair<ISupervisionListener, IBinder>>
    ) {
        service.setSupervisionEnabledForUser(expectedUserId, enabled)
        listeners.forEach { userId, (listener, binder) ->
@@ -533,7 +552,7 @@ class SupervisionServiceTest {
        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
        for ((role, packageName) in supervisionRoleHolders) {
            whenever(mockRoleManager.getRoleHoldersAsUser(eq(role), any()))
                .thenReturn(listOf(packageName));
                .thenReturn(listOf(packageName))
        }

        service.setSupervisionEnabledForUser(USER_ID, false)
@@ -549,10 +568,15 @@ class SupervisionServiceTest {
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_REMOVE_POLICIES_ON_SUPERVISION_DISABLE)
    fun clearPackageSuspensions_noSupervisionPackages_doesNothing() {
        whenever(mockRoleManager.getRoleHoldersAsUser(
            any(), any())).thenReturn(listOf())
            any(),
            any()
        )).thenReturn(listOf())
        service.setSupervisionEnabledForUser(USER_ID, false)

        verify(mockPackageManagerInternal, never()).unsuspendForSuspendingPackage(any(), any(), any())
        verify(
            mockPackageManagerInternal,
            never()
        ).unsuspendForSuspendingPackage(any(), any(), any())
    }

    private val systemSupervisionPackage: String
@@ -566,7 +590,7 @@ class SupervisionServiceTest {
                .let(ComponentName::unflattenFromString)

    private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) {
        val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
        val userInfo = UserInfo(userId, "tempUser", 0)
        userInfo.preCreated = preCreated
        lifecycle.onUserStarting(TargetUser(userInfo))
    }
@@ -630,6 +654,7 @@ private class SupervisionContextWrapper(
    val roleManagerWrapper: RoleManagerWrapper,
) : ContextWrapper(context) {
    val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()
    val permissions = mutableMapOf<String, Int>()

    override fun getSystemService(name: String): Any? {
        var ret = when (name) {
@@ -637,7 +662,7 @@ private class SupervisionContextWrapper(
            Context.ROLE_SERVICE -> roleManagerWrapper
            else -> super.getSystemService(name)
        }
        return ret;
        return ret
    }

    override fun getPackageManager() = pkgManager
@@ -660,14 +685,21 @@ private class SupervisionContextWrapper(
        val pendingResult =
            BroadcastReceiver.PendingResult(
                Activity.RESULT_OK,
                /* resultData= */ "",
                /* resultExtras= */ null,
                /* type= */ 0,
                /* ordered= */ true,
                /* sticky= */ false,
                /* token= */ null,
                /* resultData= */
                "",
                /* resultExtras= */
                null,
                /* type= */
                0,
                /* ordered= */
                true,
                /* sticky= */
                false,
                /* token= */
                null,
                user.identifier,
                /* flags= */ 0,
                /* flags= */
                0,
            )
        for ((receiver, filter) in interceptors) {
            if (filter.match(contentResolver, intent, false, "") > 0) {
@@ -676,4 +708,8 @@ private class SupervisionContextWrapper(
            }
        }
    }

    override fun checkCallingOrSelfPermission(permission: String): Int {
        return permissions[permission] ?: super.checkCallingOrSelfPermission(permission)
    }
}