blob: 215646778778c4f1baa0a22bbc196086a0fd81da [file] [log] [blame]
/*
* 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.internal.pm.pkg.component.ParsedUsesPermission;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
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();
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;
}
}
}