diff options
| author | 2023-07-28 01:14:00 +0000 | |
|---|---|---|
| committer | 2023-07-28 01:14:00 +0000 | |
| commit | 10b34d0e80d3150f52ba60d66807aa932475387e (patch) | |
| tree | 050a0705f1c82854629f123dda60d6d9affa97d5 | |
| parent | 9d53b1cd92f02fba72554b9d69b882ac8f766716 (diff) | |
| parent | a61b578f97ec7f59ba6bdfbb51fa8cf26ed09bd0 (diff) | |
Merge "Allow system installer to deny update ownership" into udc-dev am: a61b578f97
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24215103
Change-Id: Id64c8621db57ed6d084fc1a48c28a21d1157a248
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
7 files changed, 243 insertions, 4 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8fafb18393ef..2a0d4acbe76f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -234,6 +234,24 @@ public abstract class PackageManager { "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT"; /** + * Application level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for a privileged system installer to define a list of up to 500 packages that + * should not have their updates owned by any installer. The list must be provided via a default + * XML resource with the following format: + * + * <pre> + * <deny-ownership>PACKAGE_NAME</deny-ownership> + * <deny-ownership>PACKAGE_NAME</deny-ownership> + * </pre> + * + * <b>NOTE:</b> Installers that provide this property will not granted update ownership for any + * packages that they request update ownership of. + * @hide + */ + public static final String PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST = + "android.app.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST"; + + /** * A property value set within the manifest. * <p> * The value of a property will only have a single type, as defined by diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index fbd54555dbbf..622cb6609630 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -166,6 +166,7 @@ import com.android.internal.util.CollectionUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; +import com.android.server.SystemConfig; import com.android.server.art.model.DexoptParams; import com.android.server.art.model.DexoptResult; import com.android.server.pm.Installer.LegacyDexoptDisabledException; @@ -230,6 +231,7 @@ final class InstallPackageHelper { private final ViewCompiler mViewCompiler; private final SharedLibrariesImpl mSharedLibraries; private final PackageManagerServiceInjector mInjector; + private final UpdateOwnershipHelper mUpdateOwnershipHelper; // TODO(b/198166813): remove PMS dependency InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { @@ -247,6 +249,7 @@ final class InstallPackageHelper { mPackageAbiHelper = pm.mInjector.getAbiHelper(); mViewCompiler = pm.mInjector.getViewCompiler(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); + mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); } InstallPackageHelper(PackageManagerService pm) { @@ -332,6 +335,8 @@ final class InstallPackageHelper { final String updateOwnerFromSysconfig = isApex || !pkgSetting.isSystem() ? null : mPm.mInjector.getSystemConfig().getSystemAppUpdateOwnerPackageName( parsedPackage.getPackageName()); + final boolean isUpdateOwnershipDenylisted = + mUpdateOwnershipHelper.isUpdateOwnershipDenylisted(parsedPackage.getPackageName()); final boolean isUpdateOwnershipEnabled = oldUpdateOwner != null; // For standard install (install via session), the installSource isn't null. @@ -367,6 +372,9 @@ final class InstallPackageHelper { & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0; final boolean isSameUpdateOwner = TextUtils.equals(oldUpdateOwner, installSource.mInstallerPackageName); + final boolean isInstallerUpdateOwnerDenylistProvider = + mUpdateOwnershipHelper.isUpdateOwnershipDenyListProvider( + installSource.mUpdateOwnerPackageName); // Here we handle the update owner for the package, and the rules are: // -. Only enabling update ownership enforcement on initial installation if the @@ -374,13 +382,16 @@ final class InstallPackageHelper { // -. Once the installer changes and users agree to proceed, clear the update // owner (package state in other users are taken into account as well). if (!isUpdate) { - if (!isRequestUpdateOwnership) { + if (!isRequestUpdateOwnership + || isUpdateOwnershipDenylisted + || isInstallerUpdateOwnerDenylistProvider) { installSource = installSource.setUpdateOwnerPackageName(null); } else if ((!isUpdateOwnershipEnabled && pkgAlreadyExists) || (isUpdateOwnershipEnabled && !isSameUpdateOwner)) { installSource = installSource.setUpdateOwnerPackageName(null); } - } else if (!isSameUpdateOwner || !isUpdateOwnershipEnabled) { + } else if (!isSameUpdateOwner + || !isUpdateOwnershipEnabled) { installSource = installSource.setUpdateOwnerPackageName(null); } } @@ -473,6 +484,19 @@ final class InstallPackageHelper { pkgSetting.setLoadingProgress(1f); } + ArraySet<String> listItems = mUpdateOwnershipHelper.readUpdateOwnerDenyList(pkgSetting); + if (listItems != null && !listItems.isEmpty()) { + mUpdateOwnershipHelper.addToUpdateOwnerDenyList(pkgSetting.getPackageName(), listItems); + for (String unownedPackage : listItems) { + PackageSetting unownedSetting = mPm.mSettings.getPackageLPr(unownedPackage); + SystemConfig config = SystemConfig.getInstance(); + if (unownedSetting != null + && config.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) { + unownedSetting.setUpdateOwnerPackage(null); + } + } + } + return pkg; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index db47306ad58e..f2b62eaf25f8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1607,7 +1607,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService (i, pm) -> new SharedLibrariesImpl(pm, i), (i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(), i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(), - context)); + context), + (i, pm) -> new UpdateOwnershipHelper()); if (Build.VERSION.SDK_INT <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index 13549f536c9f..51840e70ed26 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -144,6 +144,7 @@ public class PackageManagerServiceInjector { private final Singleton<IBackupManager> mIBackupManager; private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer; private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer; + private final Singleton<UpdateOwnershipHelper> mUpdateOwnershipHelperProducer; PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock, Installer installer, Object installLock, PackageAbiHelper abiHelper, @@ -183,7 +184,8 @@ public class PackageManagerServiceInjector { Producer<BackgroundDexOptService> backgroundDexOptService, Producer<IBackupManager> iBackupManager, Producer<SharedLibrariesImpl> sharedLibrariesProducer, - Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer) { + Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer, + Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) { mContext = context; mLock = lock; mInstaller = installer; @@ -238,6 +240,7 @@ public class PackageManagerServiceInjector { mSharedLibrariesProducer = new Singleton<>(sharedLibrariesProducer); mCrossProfileIntentFilterHelperProducer = new Singleton<>( crossProfileIntentFilterHelperProducer); + mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer); } /** @@ -423,6 +426,11 @@ public class PackageManagerServiceInjector { return mSharedLibrariesProducer.get(this, mPackageManager); } + public UpdateOwnershipHelper getUpdateOwnershipHelper() { + return mUpdateOwnershipHelperProducer.get(this, mPackageManager); + } + + /** Provides an abstraction to static access to system state. */ public interface SystemWrapper { void disablePackageCaches(); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 10673c6f2dd2..59314a26ab97 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -312,6 +312,7 @@ final class RemovePackageHelper { synchronized (mPm.mLock) { mPm.mDomainVerificationManager.clearPackage(deletedPs.getPackageName()); mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName); + mPm.mInjector.getUpdateOwnershipHelper().removeUpdateOwnerDenyList(packageName); final Computer snapshot = mPm.snapshotComputer(); mPm.mAppsFilter.removePackage(snapshot, snapshot.getPackageStateInternal(packageName)); diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java new file mode 100644 index 000000000000..43752f31a1a2 --- /dev/null +++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2023 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 static android.content.pm.PackageManager.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST; + +import static com.android.server.pm.PackageManagerService.TAG; + +import android.Manifest; +import android.app.ResourcesManager; +import android.content.pm.ApplicationInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.component.ParsedUsesPermission; + +import org.xmlpull.v1.XmlPullParser; + +import java.util.List; + +/** Helper class for managing update ownership and optouts for the feature. */ +public class UpdateOwnershipHelper { + + // Called out in PackageManager.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST docs + private static final int MAX_DENYLIST_SIZE = 500; + private static final String TAG_OWNERSHIP_OPT_OUT = "deny-ownership"; + private final ArrayMap<String, ArraySet<String>> mUpdateOwnerOptOutsToOwners = + new ArrayMap<>(200); + + private final Object mLock = new Object(); + + private static boolean hasValidOwnershipDenyList(PackageSetting pkgSetting) { + AndroidPackage pkg = pkgSetting.getPkg(); + // we're checking for uses-permission for these priv permissions instead of grant as we're + // only considering system apps to begin with, so presumed to be granted. + return pkg != null + && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp()) + && pkg.getProperties().containsKey(PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST) + && usesAnyPermission(pkg, + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.INSTALL_PACKAGE_UPDATES); + } + + + /** Returns true if a package setting declares that it uses a permission */ + private static boolean usesAnyPermission(AndroidPackage pkgSetting, String... permissions) { + List<ParsedUsesPermission> usesPermissions = pkgSetting.getUsesPermissions(); + for (int i = 0; i < usesPermissions.size(); i++) { + for (int j = 0; j < permissions.length; j++) { + if (permissions[j].equals(usesPermissions.get(i).getName())) { + return true; + } + } + } + return false; + } + + /** + * Reads the update owner deny list from a {@link PackageSetting} and returns the set of + * packages it contains or {@code null} if it cannot be read. + */ + public ArraySet<String> readUpdateOwnerDenyList(PackageSetting pkgSetting) { + if (!hasValidOwnershipDenyList(pkgSetting)) { + return null; + } + AndroidPackage pkg = pkgSetting.getPkg(); + if (pkg == null) { + return null; + } + ArraySet<String> ownershipDenyList = new ArraySet<>(MAX_DENYLIST_SIZE); + try { + int resId = pkg.getProperties().get(PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST) + .getResourceId(); + ApplicationInfo appInfo = AndroidPackageUtils.generateAppInfoWithoutState(pkg); + Resources resources = ResourcesManager.getInstance().getResources( + null, appInfo.sourceDir, appInfo.splitSourceDirs, appInfo.resourceDirs, + appInfo.overlayPaths, appInfo.sharedLibraryFiles, null, Configuration.EMPTY, + null, null, null); + try (XmlResourceParser parser = resources.getXml(resId)) { + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + if (parser.next() == XmlResourceParser.START_TAG) { + if (TAG_OWNERSHIP_OPT_OUT.equals(parser.getName())) { + parser.next(); + String packageName = parser.getText(); + if (packageName != null && !packageName.isBlank()) { + ownershipDenyList.add(packageName); + if (ownershipDenyList.size() > MAX_DENYLIST_SIZE) { + Slog.w(TAG, "Deny list defined by " + pkg.getPackageName() + + " was trucated to maximum size of " + + MAX_DENYLIST_SIZE); + break; + } + } + } + } + } + } + } catch (Exception e) { + Slog.e(TAG, "Failed to parse update owner list for " + pkgSetting.getPackageName(), e); + return null; + } + return ownershipDenyList; + } + + /** + * Begins tracking the contents of a deny list and the owner of that deny list for use in calls + * to {@link #isUpdateOwnershipDenylisted(String)} and + * {@link #isUpdateOwnershipDenyListProvider(String)}. + * + * @param listOwner the packageName of the package that owns the deny list. + * @param listContents the list of packageNames that are on the deny list. + */ + public void addToUpdateOwnerDenyList(String listOwner, ArraySet<String> listContents) { + synchronized (mLock) { + for (int i = 0; i < listContents.size(); i++) { + String packageName = listContents.valueAt(i); + ArraySet<String> priorDenyListOwners = mUpdateOwnerOptOutsToOwners.putIfAbsent( + packageName, new ArraySet<>(new String[]{listOwner})); + if (priorDenyListOwners != null) { + priorDenyListOwners.add(listOwner); + } + } + } + } + + /** + * Stop tracking the contents of a deny list owned by the provided owner of the deny list. + * @param listOwner the packageName of the package that owns the deny list. + */ + public void removeUpdateOwnerDenyList(String listOwner) { + synchronized (mLock) { + for (int i = mUpdateOwnerOptOutsToOwners.size() - 1; i >= 0; i--) { + ArraySet<String> packageDenyListContributors = + mUpdateOwnerOptOutsToOwners.get(mUpdateOwnerOptOutsToOwners.keyAt(i)); + if (packageDenyListContributors.remove(listOwner) + && packageDenyListContributors.isEmpty()) { + mUpdateOwnerOptOutsToOwners.removeAt(i); + } + } + } + } + + /** + * Returns {@code true} if the provided package name is on a valid update ownership deny list. + */ + public boolean isUpdateOwnershipDenylisted(String packageName) { + return mUpdateOwnerOptOutsToOwners.containsKey(packageName); + } + + /** + * Returns {@code true} if the provided package name defines a valid update ownership deny list. + */ + public boolean isUpdateOwnershipDenyListProvider(String packageName) { + if (packageName == null) { + return false; + } + synchronized (mLock) { + for (int i = mUpdateOwnerOptOutsToOwners.size() - 1; i >= 0; i--) { + if (mUpdateOwnerOptOutsToOwners.valueAt(i).contains(packageName)) { + return true; + } + } + return false; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 931a2bf53b84..3c753326fb7d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -216,6 +216,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { val handler = TestHandler(null) val defaultAppProvider: DefaultAppProvider = mock() val backgroundHandler = TestHandler(null) + val updateOwnershipHelper: UpdateOwnershipHelper = mock() } companion object { @@ -303,6 +304,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.injector.handler) { mocks.handler } whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider } whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler } + whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper } wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig) whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP) whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST) |