Merge "Allow system installer to deny update ownership" into udc-dev

This commit is contained in:
Patrick Baumann
2023-07-27 23:38:06 +00:00
committed by Android (Google) Code Review
7 changed files with 243 additions and 4 deletions

View File

@@ -233,6 +233,24 @@ public abstract class PackageManager {
public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT =
"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>
* &lt;deny-ownership&gt;PACKAGE_NAME&lt;/deny-ownership&gt;
* &lt;deny-ownership&gt;PACKAGE_NAME&lt;/deny-ownership&gt;
* </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>

View File

@@ -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;
}

View File

@@ -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!");

View File

@@ -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();

View File

@@ -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));

View File

@@ -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;
}
}
}

View File

@@ -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)