Merge "Allow system installer to deny update ownership" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a61b578f97
@@ -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>
|
||||
* <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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user