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