1 /*
2  * Copyright (C) 2021 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 
17 package com.android.server.pm;
18 
19 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
20 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
21 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
22 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
23 
24 import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
25 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
26 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
27 import static com.android.server.pm.PackageManagerService.TAG;
28 
29 import android.content.pm.Flags;
30 import android.content.pm.PackageManager;
31 import android.content.pm.SharedLibraryInfo;
32 import android.content.pm.SigningDetails;
33 import android.os.Build;
34 import android.os.SystemProperties;
35 import android.util.ArrayMap;
36 import android.util.Log;
37 import android.util.Slog;
38 
39 import com.android.internal.pm.parsing.pkg.ParsedPackage;
40 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
41 import com.android.server.SystemConfig;
42 import com.android.server.pm.pkg.AndroidPackage;
43 import com.android.server.utils.WatchedLongSparseArray;
44 
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.Map;
48 
49 /**
50  * Package scan results and related request details used to reconcile the potential addition of
51  * one or more packages to the system.
52  *
53  * Reconcile will take a set of package details that need to be committed to the system and make
54  * sure that they are valid in the context of the system and the other installing apps. Any
55  * invalid state or app will result in a failed reconciliation and thus whatever operation (such
56  * as install) led to the request.
57  */
58 final class ReconcilePackageUtils {
59     // TODO(b/308573259): with allow-list, we should be able to disallow such installs even in
60     // debuggable builds.
61     private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS = Build.IS_DEBUGGABLE
62             || !Flags.restrictNonpreloadsSystemShareduids();
63 
reconcilePackages( List<InstallRequest> installRequests, Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos, SharedLibrariesImpl sharedLibraries, KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)64     public static List<ReconciledPackage> reconcilePackages(
65             List<InstallRequest> installRequests,
66             Map<String, AndroidPackage> allPackages,
67             Map<String, Settings.VersionInfo> versionInfos,
68             SharedLibrariesImpl sharedLibraries,
69             KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)
70             throws ReconcileFailure {
71         final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
72 
73         // make a copy of the existing set of packages so we can combine them with incoming packages
74         final ArrayMap<String, AndroidPackage> combinedPackages =
75                 new ArrayMap<>(allPackages.size() + installRequests.size());
76 
77         combinedPackages.putAll(allPackages);
78 
79         final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
80                 new ArrayMap<>();
81 
82         for (InstallRequest installRequest :  installRequests) {
83             installRequest.onReconcileStarted();
84 
85             // add / replace existing with incoming packages
86             combinedPackages.put(installRequest.getScannedPackageSetting().getPackageName(),
87                     installRequest.getParsedPackage());
88 
89             // in the first pass, we'll build up the set of incoming shared libraries
90             final List<SharedLibraryInfo> allowedSharedLibInfos =
91                     sharedLibraries.getAllowedSharedLibInfos(installRequest);
92             if (allowedSharedLibInfos != null) {
93                 for (SharedLibraryInfo info : allowedSharedLibInfos) {
94                     if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
95                             incomingSharedLibraries, info)) {
96                         throw ReconcileFailure.ofInternalError(
97                                 "Shared Library " + info.getName()
98                                         + " is being installed twice in this set!",
99                                 PackageManagerException.INTERNAL_ERROR_SHARED_LIB_INSTALLED_TWICE);
100                     }
101                 }
102             }
103         }
104 
105         final AndroidPackage systemPackage = allPackages.get(KnownPackages.SYSTEM_PACKAGE_NAME);
106 
107         for (InstallRequest installRequest : installRequests) {
108             final String installPackageName = installRequest.getParsedPackage().getPackageName();
109             final List<SharedLibraryInfo> allowedSharedLibInfos =
110                     sharedLibraries.getAllowedSharedLibInfos(installRequest);
111 
112             final DeletePackageAction deletePackageAction;
113             // we only want to try to delete for non system apps
114             if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
115                 final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
116                 final int deleteFlags = PackageManager.DELETE_KEEP_DATA
117                         | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
118                 deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(
119                         installRequest.getRemovedInfo(),
120                         installRequest.getOriginalPackageSetting(),
121                         installRequest.getDisabledPackageSetting(),
122                         deleteFlags, null /* all users */);
123                 if (deletePackageAction == null) {
124                     throw new ReconcileFailure(
125                             PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
126                             "May not delete " + installPackageName + " to replace");
127                 }
128             } else {
129                 deletePackageAction = null;
130             }
131 
132             final int scanFlags = installRequest.getScanFlags();
133             final int parseFlags = installRequest.getParseFlags();
134             final ParsedPackage parsedPackage = installRequest.getParsedPackage();
135             final PackageSetting disabledPkgSetting = installRequest.getDisabledPackageSetting();
136             final PackageSetting lastStaticSharedLibSetting =
137                     installRequest.getStaticSharedLibraryInfo() == null ? null
138                             : sharedLibraries.getStaticSharedLibLatestVersionSetting(
139                                     installRequest);
140             final PackageSetting signatureCheckPs =
141                     lastStaticSharedLibSetting != null
142                             ? lastStaticSharedLibSetting
143                             : installRequest.getScannedPackageSetting();
144             boolean removeAppKeySetData = false;
145             boolean sharedUserSignaturesChanged = false;
146             SigningDetails signingDetails = null;
147             if (parsedPackage != null) {
148                 signingDetails = parsedPackage.getSigningDetails();
149             }
150             final boolean isSystemPackage =
151                     ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0);
152             final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
153             SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr(
154                     signatureCheckPs);
155             if (ksms.shouldCheckUpgradeKeySetLocked(
156                     signatureCheckPs, sharedUserSetting, scanFlags)) {
157                 if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
158                     // We just determined the app is signed correctly, so bring
159                     // over the latest parsed certs.
160                 } else {
161                     if (!isSystemPackage) {
162                         throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
163                                 "Package " + parsedPackage.getPackageName()
164                                         + " upgrade keys do not match the previously installed"
165                                         + " version");
166                     } else {
167                         String msg = "System package " + parsedPackage.getPackageName()
168                                 + " signature changed; retaining data.";
169                         PackageManagerService.reportSettingsProblem(Log.WARN, msg);
170                     }
171                 }
172             } else {
173                 try {
174                     final Settings.VersionInfo versionInfo = versionInfos.get(installPackageName);
175                     final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
176                     final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
177                     final boolean isRollback = installRequest.isRollback();
178                     final boolean compatMatch =
179                             PackageManagerServiceUtils.verifySignatures(signatureCheckPs,
180                                     sharedUserSetting, disabledPkgSetting,
181                                     signingDetails, compareCompat,
182                                     compareRecover, isRollback);
183                     // The new KeySets will be re-added later in the scanning process.
184                     if (compatMatch) {
185                         removeAppKeySetData = true;
186                     }
187 
188                     if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0
189                             && !installRequest.isInstallSystem() && !isSystemPackage && !isApex
190                             && signingDetails != null
191                             && systemPackage != null && systemPackage.getSigningDetails() != null
192                             && systemPackage.getSigningDetails().checkCapability(
193                                     signingDetails,
194                                     SigningDetails.CertCapabilities.PERMISSION)) {
195                         Slog.d(TAG, "Non-preload app associated with system signature: "
196                                 + signatureCheckPs.getPackageName());
197                         if (sharedUserSetting != null && !ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS) {
198                             // Check the allow-list.
199                             var allowList = systemConfig.getPackageToSharedUidAllowList();
200                             var sharedUidName = allowList.get(signatureCheckPs.getPackageName());
201                             if (sharedUidName == null
202                                     || !sharedUserSetting.name.equals(sharedUidName)) {
203                                 var msg = "Non-preload app " + signatureCheckPs.getPackageName()
204                                         + " signed with platform signature and joining shared uid: "
205                                         + sharedUserSetting.name;
206                                 Slog.e(TAG, msg + ", allowList: " + allowList);
207                                 throw new ReconcileFailure(
208                                         INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, msg);
209                             }
210                         }
211                     }
212 
213                     // if this is is a sharedUser, check to see if the new package is signed by a
214                     // newer signing certificate than the existing one, and if so, copy over the new
215                     // details
216                     if (sharedUserSetting != null) {
217                         // Attempt to merge the existing lineage for the shared SigningDetails with
218                         // the lineage of the new package; if the shared SigningDetails are not
219                         // returned this indicates the new package added new signers to the lineage
220                         // and/or changed the capabilities of existing signers in the lineage.
221                         SigningDetails sharedSigningDetails =
222                                 sharedUserSetting.signatures.mSigningDetails;
223                         SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
224                                 signingDetails);
225                         if (mergedDetails != sharedSigningDetails) {
226                             // Use the restricted merge rule with the signing lineages from the
227                             // other packages in the sharedUserId to ensure if any revoke a
228                             // capability from a previous signer then this is reflected in the
229                             // shared lineage.
230                             for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) {
231                                 if (androidPackage.getPackageName() != null
232                                         && !androidPackage.getPackageName().equals(
233                                         parsedPackage.getPackageName())) {
234                                     mergedDetails = mergedDetails.mergeLineageWith(
235                                             androidPackage.getSigningDetails(),
236                                             MERGE_RESTRICTED_CAPABILITY);
237                                 }
238                             }
239                             sharedUserSetting.signatures.mSigningDetails =
240                                     mergedDetails;
241                         }
242                         if (sharedUserSetting.signaturesChanged == null) {
243                             sharedUserSetting.signaturesChanged = Boolean.FALSE;
244                         }
245                     }
246                 } catch (PackageManagerException e) {
247                     if (!isSystemPackage) {
248                         throw new ReconcileFailure(e);
249                     }
250                     signingDetails = parsedPackage.getSigningDetails();
251 
252                     // If the system app is part of a shared user we allow that shared user to
253                     // change
254                     // signatures as well as part of an OTA. We still need to verify that the
255                     // signatures
256                     // are consistent within the shared user for a given boot, so only allow
257                     // updating
258                     // the signatures on the first package scanned for the shared user (i.e. if the
259                     // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
260                     if (sharedUserSetting != null) {
261                         if (sharedUserSetting.signaturesChanged != null
262                                 && !PackageManagerServiceUtils.canJoinSharedUserId(
263                                 parsedPackage.getPackageName(), parsedPackage.getSigningDetails(),
264                                 sharedUserSetting,
265                                 PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) {
266                             if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
267                                 // Mismatched signatures is an error and silently skipping system
268                                 // packages will likely break the device in unforeseen ways.
269                                 // However, we allow the device to boot anyway because, prior to Q,
270                                 // vendors were not expecting the platform to crash in this
271                                 // situation.
272                                 // This WILL be a hard failure on any new API levels after Q.
273                                 throw new ReconcileFailure(
274                                         INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
275                                         "Signature mismatch for shared user: "
276                                                 + sharedUserSetting);
277                             } else {
278                                 // Treat mismatched signatures on system packages using a shared
279                                 // UID as
280                                 // fatal for the system overall, rather than just failing to install
281                                 // whichever package happened to be scanned later.
282                                 throw new IllegalStateException(
283                                         "Signature mismatch on system package "
284                                                 + parsedPackage.getPackageName()
285                                                 + " for shared user "
286                                                 + sharedUserSetting);
287                             }
288                         }
289 
290                         sharedUserSignaturesChanged = true;
291                         sharedUserSetting.signatures.mSigningDetails =
292                                 parsedPackage.getSigningDetails();
293                         sharedUserSetting.signaturesChanged = Boolean.TRUE;
294                     }
295                     // File a report about this.
296                     String msg = "System package " + parsedPackage.getPackageName()
297                             + " signature changed; retaining data.";
298                     PackageManagerService.reportSettingsProblem(Log.WARN, msg);
299                 } catch (IllegalArgumentException e) {
300                     // should never happen: certs matched when checking, but not when comparing
301                     // old to new for sharedUser
302                     throw new RuntimeException(
303                             "Signing certificates comparison made on incomparable signing details"
304                                     + " but somehow passed verifySignatures!", e);
305                 }
306             }
307 
308             final ReconciledPackage reconciledPackage =
309                     new ReconciledPackage(installRequests, allPackages, installRequest,
310                             deletePackageAction, allowedSharedLibInfos, signingDetails,
311                             sharedUserSignaturesChanged, removeAppKeySetData);
312 
313             // Check all shared libraries and map to their actual file path.
314             // We only do this here for apps not on a system dir, because those
315             // are the only ones that can fail an install due to this.  We
316             // will take care of the system apps by updating all of their
317             // library paths after the scan is done. Also during the initial
318             // scan don't update any libs as we do this wholesale after all
319             // apps are scanned to avoid dependency based scanning.
320             if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0
321                     && (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
322                     == 0) {
323                 try {
324                     reconciledPackage.mCollectedSharedLibraryInfos =
325                             sharedLibraries.collectSharedLibraryInfos(
326                                     installRequest.getParsedPackage(), combinedPackages,
327                                     incomingSharedLibraries);
328                 } catch (PackageManagerException e) {
329                     throw new ReconcileFailure(e.error, e.getMessage());
330                 }
331             }
332 
333             installRequest.onReconcileFinished();
334             result.add(reconciledPackage);
335         }
336 
337         return result;
338     }
339 
340     /**
341      * If the database version for this type of package (internal storage or
342      * external storage) is less than the version where package signatures
343      * were updated, return true.
344      */
isCompatSignatureUpdateNeeded(Settings.VersionInfo ver)345     public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
346         return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
347     }
348 
isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver)349     public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
350         return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
351     }
352 }
353