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

Commit 90702836 authored by Oli Lan's avatar Oli Lan
Browse files

Prevent non-admin users from deleting system apps.

This addresses a security issue where the guest user can remove updates
for system apps.

With this CL, attempts to uninstall/downgrade system apps will fail if
attempted by a non-admin user.

Bug: 170646036
Test: atest DeletePackageHelperTest
Test: manual, try uninstalling system app update as guest
Change-Id: I4e959e296cca9bbdfc8fccc5e5e0e654ca524165
parent c5b61d93
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.content.pm.PackageChangeEvent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
import android.net.Uri;
import android.os.Binder;
@@ -161,6 +162,15 @@ final class DeletePackageHelper {
                return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
            }

            if (PackageManagerServiceUtils.isSystemApp(uninstalledPs)) {
                UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
                if (userInfo == null || !userInfo.isAdmin()) {
                    Slog.w(TAG, "Not removing package " + packageName
                            + " as only admin user may downgrade system apps");
                    return PackageManager.DELETE_FAILED_USER_RESTRICTED;
                }
            }

            disabledSystemPs = mPm.mSettings.getDisabledSystemPkgLPr(packageName);
            // Static shared libs can be declared by any package, so let us not
            // allow removing a package if it provides a lib others depend on.
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.pm

import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.os.Build
import android.util.Log
import com.android.server.testutils.any
import com.android.server.testutils.spy
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
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.Mockito.doAnswer

@RunWith(JUnit4::class)
class DeletePackageHelperTest {

    @Rule
    @JvmField
    val rule = MockSystemRule()

    private lateinit var mPms: PackageManagerService
    private lateinit var mUserManagerInternal: UserManagerInternal

    @Before
    @Throws(Exception::class)
    fun setup() {
        Log.i("system.out", "setup", Exception())
        rule.system().stageNominalSystemState()
        rule.system().stageScanExistingPackage(
            "a.data.package", 1L, rule.system().dataAppDirectory)

        mUserManagerInternal = rule.mocks().injector.userManagerInternal
        whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1))

        mPms = createPackageManagerService()
        doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any())
        doAnswer { null }.`when`(mPms).freezePackageForDelete(any(), any(), any(), any())
    }

    private fun createPackageManagerService(): PackageManagerService {
        return spy(PackageManagerService(rule.mocks().injector,
            false /*coreOnly*/,
            false /*factoryTest*/,
            MockSystem.DEFAULT_VERSION_INFO.fingerprint,
            false /*isEngBuild*/,
            false /*isUserDebugBuild*/,
            Build.VERSION_CODES.CUR_DEVELOPMENT,
            Build.VERSION.INCREMENTAL))
    }

    @Test
    fun deleteSystemPackageFailsIfNotAdmin() {
        val ps = mPms.mSettings.getPackageLPr("a.data.package")
        whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
        whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))

        val dph = DeletePackageHelper(mPms)
        val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false)

        assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
    }

    @Test
    fun deleteSystemPackageSucceedsIfAdmin() {
        val ps = mPms.mSettings.getPackageLPr("a.data.package")
        whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
        whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(
            UserInfo(1, "test", UserInfo.FLAG_ADMIN))

        val dph = DeletePackageHelper(mPms)
        val result = dph.deletePackageX("a.data.package", 1L, 1,
            PackageManager.DELETE_SYSTEM_APP, false)

        assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
    }
}
 No newline at end of file