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

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

Merge "[ADI][43/N] preserve developer verification policy across reboots" into main

parents 9eef798c b0d8d42d
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -98,7 +98,6 @@ interface IPackageInstaller {

    @PermissionManuallyEnforced
    int getDeveloperVerificationPolicy(int userId);
    @PermissionManuallyEnforced
    boolean setDeveloperVerificationPolicy(int policy, int userId);
    ComponentName getDeveloperVerificationServiceProvider();
    @EnforcePermission("DEVELOPER_VERIFICATION_AGENT")
+55 −14
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.TypedXmlPullParser;
@@ -176,6 +177,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements

    /** XML constants used in {@link #mSessionsFile} */
    private static final String TAG_SESSIONS = "sessions";
    private static final String TAG_DEVELOPER_VERIFICATION_POLICY_PER_USER =
            "developerVerificationPolicyPerUser";
    private static final String ATTR_USER_ID = "userId";
    private static final String ATTR_DEVELOPER_VERIFICATION_POLICY = "developerVerificationPolicy";

    /** Automatically destroy sessions older than this */
    private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
@@ -289,7 +294,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
    /**
     * Default verification policy for incoming installation sessions, mapped from userId to policy.
     */
    @GuardedBy("mVerificationPolicyPerUser")
    @GuardedBy("mDeveloperVerificationPolicyPerUser")
    private final SparseIntArray mDeveloperVerificationPolicyPerUser = new SparseIntArray(1);
    /**
     * Default developer verification policy for a new user.
@@ -353,13 +358,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        mPackageArchiver = new PackageArchiver(mContext, mPm);
        mDeveloperVerifierController = DeveloperVerifierController.getInstance(context,
                mInstallHandler, developerVerificationServiceProvider);
        synchronized (mDeveloperVerificationPolicyPerUser) {
            int[] users = mPm.mUserManager.getUserIds();
            for (int i = 0; i < users.length; i++) {
                // TODO(b/360129657): preserve the overridden policy across reboots.
                mDeveloperVerificationPolicyPerUser.put(users[i], DEFAULT_VERIFICATION_POLICY);
            }
        }
        mInstallDependencyHelper = new InstallDependencyHelper(mContext,
                mPm.mInjector.getSharedLibrariesImpl(), this);

@@ -404,12 +402,30 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                Slog.w(TAG, "Deleting orphan icon " + icon);
                icon.delete();
            }
        }

        // Clean up the per-user developer verification policies in case some previous users have
        // been removed since the last reboot.
        final int[] users = mPm.mUserManager.getUserIds();
        synchronized (mDeveloperVerificationPolicyPerUser) {
            int size = mDeveloperVerificationPolicyPerUser.size();
            for (int i = size - 1; i >= 0; i--) {
                if (!ArrayUtils.contains(users, mDeveloperVerificationPolicyPerUser.keyAt(i))) {
                    mDeveloperVerificationPolicyPerUser.removeAt(i);
                }
            }
            // If the per-user developer verification policy was never set before for any user,
            // add a default policy for the user.
            for (int i = 0; i < users.length; i++) {
                if (mDeveloperVerificationPolicyPerUser.indexOfKey(users[i]) < 0) {
                    mDeveloperVerificationPolicyPerUser.put(users[i], DEFAULT_VERIFICATION_POLICY);
                }
            }
        }

        // Invalid sessions might have been marked while parsing. Re-write the database with
        // the updated information.
        mSettingsWriteRequest.runNow();

        }
    }

    private void onBroadcastReady() {
@@ -570,6 +586,19 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                        }
                        mSessions.put(session.sessionId, session);
                        mAllocatedSessions.put(session.sessionId, true);
                    } else if (TAG_DEVELOPER_VERIFICATION_POLICY_PER_USER.equals(tag)) {
                        int userId = in.getAttributeInt(null, ATTR_USER_ID, -1);
                        if (userId == -1) {
                            continue;
                        }
                        int policy =
                                in.getAttributeInt(null, ATTR_DEVELOPER_VERIFICATION_POLICY, -1);
                        if (policy == -1) {
                            continue;
                        }
                        synchronized (mDeveloperVerificationPolicyPerUser) {
                            mDeveloperVerificationPolicyPerUser.put(userId, policy);
                        }
                    }
                }
            }
@@ -674,6 +703,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                session.write(out, mSessionsDir);
            }
            out.endTag(null, TAG_SESSIONS);

            // Preserve current developer verification policy per user to disk.
            synchronized (mDeveloperVerificationPolicyPerUser) {
                for (int i = 0; i < mDeveloperVerificationPolicyPerUser.size(); i++) {
                    out.startTag(null, TAG_DEVELOPER_VERIFICATION_POLICY_PER_USER);
                    out.attributeInt(null, ATTR_USER_ID,
                            mDeveloperVerificationPolicyPerUser.keyAt(i));
                    out.attributeInt(null, ATTR_DEVELOPER_VERIFICATION_POLICY,
                            mDeveloperVerificationPolicyPerUser.valueAt(i));
                    out.endTag(null, TAG_DEVELOPER_VERIFICATION_POLICY_PER_USER);
                }
            }
            out.endDocument();

            mSessionsFile.finishWrite(fos);
@@ -1993,8 +2034,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
    }

    @Override
    @RequiresPermission(value = Manifest.permission.DEVELOPER_VERIFICATION_AGENT,
            conditional = true)
    public boolean setDeveloperVerificationPolicy(
            @PackageInstaller.DeveloperVerificationPolicy int policy, int userId) {
        // Write is more restrictive than read. Having the permission granted is not enough.
@@ -2021,6 +2060,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
            }
            if (policy != mDeveloperVerificationPolicyPerUser.get(userId)) {
                mDeveloperVerificationPolicyPerUser.put(userId, policy);
                // Preserve the change to disk
                mSettingsWriteRequest.schedule();
            }
        }
        return true;
+3 −1
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import com.android.internal.pm.parsing.pkg.PackageImpl
import com.android.internal.pm.parsing.pkg.ParsedPackage
import com.android.internal.pm.pkg.parsing.ParsingPackage
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
import com.android.server.IoThread
import com.android.server.LocalManagerRegistry
import com.android.server.LocalServices
import com.android.server.LockGuard
@@ -75,8 +76,8 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.resolution.ComponentResolver
import com.android.server.pm.snapshot.PackageDataSnapshot
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.developer.DeveloperVerifierController
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.sdksandbox.SdkSandboxManagerLocal
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
@@ -158,6 +159,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
                .mockStatic(DeviceConfig::class.java, Mockito.CALLS_REAL_METHODS)
                .mockStatic(HexEncoding::class.java)
                .mockStatic(DeveloperVerifierController::class.java)
                .mockStatic(IoThread::class.java)
                .apply(withSession)
        session = apply.startMocking()
        whenever(mocks.settings.insertPackageSettingLPw(
+177 −11
Original line number Diff line number Diff line
@@ -28,38 +28,63 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInstaller;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.PermissionEnforcer;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;

import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.pm.verify.developer.DeveloperVerifierController;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;

@Presubmit
@RunWith(JUnit4.class)
@RunWith(AndroidJUnit4.class)
public class PackageInstallerServiceTest {
    private @Mock PermissionEnforcer mMockPermissionEnforcer;
    private @Mock ActivityManager mMockActivityManager;
    private @Mock AppOpsManager mAppOpsManager;
    private @Mock SystemServiceManager mMockSystemServiceManager;
    private @Mock DeveloperVerifierController mMockDeveloperVerifierController;
    private @Mock File mMockeFile;
    private String mPackageName;
    private PackageManagerService mPms;
    private @Mock Computer mMockSnapshot;
    private @Mock Handler mMockHandler;
    private File mTestDir;
    private final PackageManagerServiceTestParams mTestParams =
            new PackageManagerServiceTestParams();
    private static final int TEST_USER_ID = 10;
    private static final int TEST_USER_ID_2 = 20;
    private static final int TEST_USER_ID_3 = 30;
    private static final int TEST_USER_ID_4 = 40;

    @Rule
    public final MockSystemRule rule = new MockSystemRule();
@@ -69,37 +94,72 @@ public class PackageInstallerServiceTest {
        MockitoAnnotations.initMocks(this);
        rule.system().stageNominalSystemState();
        mPackageName = this.getClass().getPackageName();
        PackageManagerServiceTestParams testParams = new PackageManagerServiceTestParams();
        testParams.packages = new ArrayMap<>();
        mTestParams.packages = new ArrayMap<>();
        // Basic mocks to support session creation
        when(rule.mocks().getContext().getSystemService(Context.PERMISSION_ENFORCER_SERVICE))
                .thenReturn(mMockPermissionEnforcer);
        when(rule.mocks().getContext().getSystemService(ActivityManager.class))
                .thenReturn(mMockActivityManager);
        when(rule.mocks().getContext().getSystemService(AppOpsManager.class))
                .thenReturn(mAppOpsManager);
        doReturn(mMockeFile).when(() -> Environment.getDataAppDirectory(nullable(String.class)));
        doReturn(mMockeFile).when(
                () -> Environment.getDataStagingDirectory(nullable(String.class)));
        doReturn(mMockSystemServiceManager).when(
                () -> LocalServices.getService(SystemServiceManager.class));
        // Run scheduled write tasks right away
        doReturn(mMockHandler).when(IoThread::getHandler);
        when(mMockHandler.post(any(Runnable.class))).thenAnswer(
                i -> {
                    ((Runnable) i.getArguments()[0]).run();
                    return true;
                });
        doReturn(mMockDeveloperVerifierController).when(
                () -> DeveloperVerifierController.getInstance(any(), any(), argThat(
                        componentName -> componentName.getPackageName().equals(mPackageName)
                () -> DeveloperVerifierController.getInstance(any(Context.class),
                        any(Handler.class), argThat(componentName
                                -> componentName.getPackageName().equals(mPackageName)
                ))
        );
        doReturn(mMockSystemServiceManager).when(
                () -> LocalServices.getService(SystemServiceManager.class));
        // Test specific environment setup
        doReturn(mPackageName).when(mMockDeveloperVerifierController).getVerifierPackageName();
        mPms = spy(new PackageManagerService(rule.mocks().getInjector(), testParams));
        mPms = spy(new PackageManagerService(rule.mocks().getInjector(), mTestParams));
        doReturn(false).when(mPms).isUserRestricted(anyInt(), nullable(String.class));
        doReturn(mMockSnapshot).when(mPms).snapshotComputer();
        doReturn(myUid()).when(mMockSnapshot).getPackageUidInternal(
                eq(mPackageName), anyLong(), anyInt(), anyInt());
        // Create a test file for read/write of mSessionsFile
        mTestDir = new File(InstrumentationRegistry.getInstrumentation().getTargetContext()
                .getFilesDir(), "testDir");
        assertThat(mTestDir.mkdirs()).isTrue();
        doReturn(mTestDir).when(Environment::getDataSystemDirectory);
    }

    @After
    public void tearDown() {
        if (mTestDir != null) {
            // Clean up test dir to remove persisted user files.
            FileUtils.deleteContentsAndDir(mTestDir);
        }
    }

    @Test
    public void testVerificationPolicyPerUser() {
        PackageInstallerService service = new PackageInstallerService(
        doReturn(new int[] {UserHandle.USER_SYSTEM, TEST_USER_ID, TEST_USER_ID_2})
                .when(mPms.mUserManager).getUserIds();
        final PackageInstallerService service = new PackageInstallerService(
                rule.mocks().getContext(), mPms, null,
                new ComponentName(mPackageName, this.getClass().getName()));
        service.systemReady();
        final int defaultPolicy = service.getDeveloperVerificationPolicy(
                /* userId= */ UserHandle.USER_SYSTEM);
        assertThat(defaultPolicy).isAtLeast(PackageInstaller.DEVELOPER_VERIFICATION_POLICY_NONE);
        assertThat(service.getDeveloperVerificationPolicy(TEST_USER_ID)).isEqualTo(defaultPolicy);
        assertThat(service.getDeveloperVerificationPolicy(TEST_USER_ID_2)).isEqualTo(defaultPolicy);
        assertThat(service.setDeveloperVerificationPolicy(
                /* policy= */ PackageInstaller.DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
                /* userId= */ UserHandle.USER_SYSTEM)).isTrue();
        // Test with a non-existing user
        final int newUserId = 1;
        final int newUserId = TEST_USER_ID_3;
        assertThrows(IllegalStateException.class, () -> service.getDeveloperVerificationPolicy(
                /* userId= */ newUserId));
        assertThrows(IllegalStateException.class,
@@ -126,6 +186,112 @@ public class PackageInstallerServiceTest {
                        /* userId= */ newUserId));
    }

    @Test
    public void testWriteAndReadVerificationPolicyPerUser() {
        doReturn(new int[] {UserHandle.USER_SYSTEM, TEST_USER_ID, TEST_USER_ID_2})
                .when(mPms.mUserManager).getUserIds();
        final PackageInstallerService service = new PackageInstallerService(
                rule.mocks().getContext(), mPms, null,
                new ComponentName(mPackageName, this.getClass().getName()));
        service.systemReady();
        final int defaultPolicy = service.getDeveloperVerificationPolicy(
                /* userId= */ UserHandle.USER_SYSTEM);
        // Modify the policy for each user
        final int policyForSystemUser =
                PackageInstaller.DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
        final int policyForTestUser =
                PackageInstaller.DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_WARN;
        final int policyForTestUser2 =
                PackageInstaller.DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_OPEN;
        assertThat(service.setDeveloperVerificationPolicy(
                /* policy= */ policyForSystemUser,
                /* userId= */ UserHandle.USER_SYSTEM)).isTrue();
        assertThat(service.setDeveloperVerificationPolicy(
                /* policy= */ policyForTestUser,
                /* userId= */ TEST_USER_ID)).isTrue();
        assertThat(service.setDeveloperVerificationPolicy(
                /* policy= */ policyForTestUser2,
                /* userId= */ TEST_USER_ID_2)).isTrue();
        // systemReady and Each policy change above should have triggered a write to the test file
        // but since the write in systemReady is done immediately in the main thread of
        // PackageInstallerService and doesn't go through the handler of the IO thread, we don't
        // have the chance to verify that here.
        verify(mMockHandler, times(3)).post(any(Runnable.class));
        // Mimic a reboot by creating a new service instance with the latest policy for each user
        // loaded from the test file
        final PackageInstallerService service2 = new PackageInstallerService(
                rule.mocks().getContext(), mPms, null,
                new ComponentName(mPackageName, this.getClass().getName()));
        service2.systemReady();
        assertThat(service2.getDeveloperVerificationPolicy(UserHandle.USER_SYSTEM)).isEqualTo(
                policyForSystemUser);
        assertThat(service2.getDeveloperVerificationPolicy(TEST_USER_ID)).isEqualTo(
                policyForTestUser);
        assertThat(service2.getDeveloperVerificationPolicy(TEST_USER_ID_2)).isEqualTo(
                policyForTestUser2);
    }

    // Mimic a system where it initially has 2 users, then 1 is removed and a new one is added
    // on reboot. Then all are removed and a different one is added on reboot. Test that the
    // per-user developer verification policy is correctly set.
    @Test
    public void testCleanUpVerificationPolicyPerUser() {
        doReturn(new int[] {UserHandle.USER_SYSTEM, TEST_USER_ID})
                .when(mPms.mUserManager).getUserIds();
        PackageInstallerService service = new PackageInstallerService(
                rule.mocks().getContext(), mPms, null,
                new ComponentName(mPackageName, this.getClass().getName()));
        service.systemReady();
        final int defaultPolicy = service.getDeveloperVerificationPolicy(
                /* userId= */ UserHandle.USER_SYSTEM);
        // Modify the policy for each user
        final int policyForSystemUser =
                PackageInstaller.DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
        final int policyForTestUser =
                PackageInstaller.DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_WARN;
        assertThat(service.setDeveloperVerificationPolicy(
                /* policy= */ policyForSystemUser,
                /* userId= */ UserHandle.USER_SYSTEM)).isTrue();
        assertThat(service.setDeveloperVerificationPolicy(
                /* policy= */ policyForTestUser,
                /* userId= */ TEST_USER_ID)).isTrue();
        // Mimic a reboot by creating a new service instance. The policy for the deleted user
        // should not exist, and the policy for the new user should be the default policy.
        // And previously set policy should be preserved on the remaining user.
        PackageManagerService mPms2 = spy(new PackageManagerService(rule.mocks().getInjector(),
                mTestParams));
        doReturn(new int[] {UserHandle.USER_SYSTEM, TEST_USER_ID_2})
                .when(mPms2.mUserManager).getUserIds();
        final PackageInstallerService service2 = new PackageInstallerService(
                rule.mocks().getContext(), mPms2, null,
                new ComponentName(mPackageName, this.getClass().getName()));
        service2.systemReady();
        assertThat(service2.getDeveloperVerificationPolicy(UserHandle.USER_SYSTEM)).isEqualTo(
                policyForSystemUser);
        assertThrows(IllegalStateException.class, () -> service2.getDeveloperVerificationPolicy(
                TEST_USER_ID));
        assertThat(service2.getDeveloperVerificationPolicy(TEST_USER_ID_2)).isEqualTo(
                defaultPolicy);
        // Mimic another reboot by creating a new service instance. The policy for the deleted users
        // should not exist, and the policy for the new users should be the default policy.
        PackageManagerService mPms3 = spy(new PackageManagerService(rule.mocks().getInjector(),
                mTestParams));
        doReturn(new int[] {TEST_USER_ID_3, TEST_USER_ID_4})
                .when(mPms3.mUserManager).getUserIds();
        final PackageInstallerService service3 = new PackageInstallerService(
                rule.mocks().getContext(), mPms3, null,
                new ComponentName(mPackageName, this.getClass().getName()));
        service3.systemReady();
        assertThrows(IllegalStateException.class, () -> service3.getDeveloperVerificationPolicy(
                UserHandle.USER_SYSTEM));
        assertThrows(IllegalStateException.class, () -> service3.getDeveloperVerificationPolicy(
                TEST_USER_ID_2));
        assertThat(service3.getDeveloperVerificationPolicy(TEST_USER_ID_3)).isEqualTo(
                defaultPolicy);
        assertThat(service3.getDeveloperVerificationPolicy(TEST_USER_ID_4)).isEqualTo(
                defaultPolicy);
    }

    @Test
    public void testVerifierIsNullThrowsException() {
        doReturn(mMockDeveloperVerifierController).when(