1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.pm; 17 18 import static android.content.pm.PackageManager.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST; 19 20 import static com.android.server.pm.PackageManagerService.TAG; 21 22 import android.Manifest; 23 import android.app.ResourcesManager; 24 import android.content.pm.ApplicationInfo; 25 import android.content.res.Configuration; 26 import android.content.res.Resources; 27 import android.content.res.XmlResourceParser; 28 import android.util.ArrayMap; 29 import android.util.ArraySet; 30 import android.util.Slog; 31 32 import com.android.internal.pm.pkg.component.ParsedUsesPermission; 33 import com.android.server.pm.parsing.pkg.AndroidPackageUtils; 34 import com.android.server.pm.pkg.AndroidPackage; 35 36 import org.xmlpull.v1.XmlPullParser; 37 38 import java.util.List; 39 40 /** Helper class for managing update ownership and optouts for the feature. */ 41 public class UpdateOwnershipHelper { 42 43 // Called out in PackageManager.PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST docs 44 private static final int MAX_DENYLIST_SIZE = 500; 45 private static final String TAG_OWNERSHIP_OPT_OUT = "deny-ownership"; 46 private final ArrayMap<String, ArraySet<String>> mUpdateOwnerOptOutsToOwners = 47 new ArrayMap<>(200); 48 49 private final Object mLock = new Object(); 50 hasValidOwnershipDenyList(PackageSetting pkgSetting)51 static boolean hasValidOwnershipDenyList(PackageSetting pkgSetting) { 52 AndroidPackage pkg = pkgSetting.getPkg(); 53 // we're checking for uses-permission for these priv permissions instead of grant as we're 54 // only considering system apps to begin with, so presumed to be granted. 55 return pkg != null 56 && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp()) 57 && pkg.getProperties().containsKey(PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST) 58 && usesAnyPermission(pkg, 59 Manifest.permission.INSTALL_PACKAGES, 60 Manifest.permission.INSTALL_PACKAGE_UPDATES); 61 } 62 63 64 /** Returns true if a package setting declares that it uses a permission */ usesAnyPermission(AndroidPackage pkgSetting, String... permissions)65 private static boolean usesAnyPermission(AndroidPackage pkgSetting, String... permissions) { 66 List<ParsedUsesPermission> usesPermissions = pkgSetting.getUsesPermissions(); 67 for (int i = 0; i < usesPermissions.size(); i++) { 68 for (int j = 0; j < permissions.length; j++) { 69 if (permissions[j].equals(usesPermissions.get(i).getName())) { 70 return true; 71 } 72 } 73 } 74 return false; 75 } 76 77 /** 78 * Reads the update owner deny list from a {@link PackageSetting} and returns the set of 79 * packages it contains or {@code null} if it cannot be read. 80 */ readUpdateOwnerDenyList(PackageSetting pkgSetting)81 public ArraySet<String> readUpdateOwnerDenyList(PackageSetting pkgSetting) { 82 if (!hasValidOwnershipDenyList(pkgSetting)) { 83 return null; 84 } 85 AndroidPackage pkg = pkgSetting.getPkg(); 86 if (pkg == null) { 87 return null; 88 } 89 ArraySet<String> ownershipDenyList = new ArraySet<>(MAX_DENYLIST_SIZE); 90 try { 91 int resId = pkg.getProperties().get(PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST) 92 .getResourceId(); 93 ApplicationInfo appInfo = AndroidPackageUtils.generateAppInfoWithoutState(pkg); 94 Resources resources = ResourcesManager.getInstance().getResources( 95 null, appInfo.sourceDir, appInfo.splitSourceDirs, appInfo.resourceDirs, 96 appInfo.overlayPaths, appInfo.sharedLibraryFiles, null, Configuration.EMPTY, 97 null, null, null); 98 try (XmlResourceParser parser = resources.getXml(resId)) { 99 while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { 100 if (parser.next() == XmlResourceParser.START_TAG) { 101 if (TAG_OWNERSHIP_OPT_OUT.equals(parser.getName())) { 102 parser.next(); 103 String packageName = parser.getText(); 104 if (packageName != null && !packageName.isBlank()) { 105 ownershipDenyList.add(packageName); 106 if (ownershipDenyList.size() > MAX_DENYLIST_SIZE) { 107 Slog.w(TAG, "Deny list defined by " + pkg.getPackageName() 108 + " was trucated to maximum size of " 109 + MAX_DENYLIST_SIZE); 110 break; 111 } 112 } 113 } 114 } 115 } 116 } 117 } catch (Exception e) { 118 Slog.e(TAG, "Failed to parse update owner list for " + pkgSetting.getPackageName(), e); 119 return null; 120 } 121 return ownershipDenyList; 122 } 123 124 /** 125 * Begins tracking the contents of a deny list and the owner of that deny list for use in calls 126 * to {@link #isUpdateOwnershipDenylisted(String)} and 127 * {@link #isUpdateOwnershipDenyListProvider(String)}. 128 * 129 * @param listOwner the packageName of the package that owns the deny list. 130 * @param listContents the list of packageNames that are on the deny list. 131 */ addToUpdateOwnerDenyList(String listOwner, ArraySet<String> listContents)132 public void addToUpdateOwnerDenyList(String listOwner, ArraySet<String> listContents) { 133 synchronized (mLock) { 134 for (int i = 0; i < listContents.size(); i++) { 135 String packageName = listContents.valueAt(i); 136 ArraySet<String> priorDenyListOwners = mUpdateOwnerOptOutsToOwners.putIfAbsent( 137 packageName, new ArraySet<>(new String[]{listOwner})); 138 if (priorDenyListOwners != null) { 139 priorDenyListOwners.add(listOwner); 140 } 141 } 142 } 143 } 144 145 /** 146 * Stop tracking the contents of a deny list owned by the provided owner of the deny list. 147 * @param listOwner the packageName of the package that owns the deny list. 148 */ removeUpdateOwnerDenyList(String listOwner)149 public void removeUpdateOwnerDenyList(String listOwner) { 150 synchronized (mLock) { 151 for (int i = mUpdateOwnerOptOutsToOwners.size() - 1; i >= 0; i--) { 152 ArraySet<String> packageDenyListContributors = 153 mUpdateOwnerOptOutsToOwners.get(mUpdateOwnerOptOutsToOwners.keyAt(i)); 154 if (packageDenyListContributors.remove(listOwner) 155 && packageDenyListContributors.isEmpty()) { 156 mUpdateOwnerOptOutsToOwners.removeAt(i); 157 } 158 } 159 } 160 } 161 162 /** 163 * Returns {@code true} if the provided package name is on a valid update ownership deny list. 164 */ isUpdateOwnershipDenylisted(String packageName)165 public boolean isUpdateOwnershipDenylisted(String packageName) { 166 return mUpdateOwnerOptOutsToOwners.containsKey(packageName); 167 } 168 169 /** 170 * Returns {@code true} if the provided package name defines a valid update ownership deny list. 171 */ isUpdateOwnershipDenyListProvider(String packageName)172 public boolean isUpdateOwnershipDenyListProvider(String packageName) { 173 if (packageName == null) { 174 return false; 175 } 176 synchronized (mLock) { 177 for (int i = mUpdateOwnerOptOutsToOwners.size() - 1; i >= 0; i--) { 178 if (mUpdateOwnerOptOutsToOwners.valueAt(i).contains(packageName)) { 179 return true; 180 } 181 } 182 return false; 183 } 184 } 185 } 186