blob: b6de0e5c030f0db95c8f6628fca2376e8d48066a [file] [log] [blame]
/*
* Copyright (C) 2021 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.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
import static com.android.server.pm.PackageManagerService.TAG;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.os.Build;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.utils.WatchedLongSparseArray;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Package scan results and related request details used to reconcile the potential addition of
* one or more packages to the system.
*
* Reconcile will take a set of package details that need to be committed to the system and make
* sure that they are valid in the context of the system and the other installing apps. Any
* invalid state or app will result in a failed reconciliation and thus whatever operation (such
* as install) led to the request.
*/
final class ReconcilePackageUtils {
private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true;
public static List<ReconciledPackage> reconcilePackages(
List<InstallRequest> installRequests,
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms, Settings settings)
throws ReconcileFailure {
final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
// make a copy of the existing set of packages so we can combine them with incoming packages
final ArrayMap<String, AndroidPackage> combinedPackages =
new ArrayMap<>(allPackages.size() + installRequests.size());
combinedPackages.putAll(allPackages);
final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
new ArrayMap<>();
for (InstallRequest installRequest : installRequests) {
installRequest.onReconcileStarted();
// add / replace existing with incoming packages
combinedPackages.put(installRequest.getScannedPackageSetting().getPackageName(),
installRequest.getParsedPackage());
// in the first pass, we'll build up the set of incoming shared libraries
final List<SharedLibraryInfo> allowedSharedLibInfos =
sharedLibraries.getAllowedSharedLibInfos(installRequest);
if (allowedSharedLibInfos != null) {
for (SharedLibraryInfo info : allowedSharedLibInfos) {
if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
incomingSharedLibraries, info)) {
throw ReconcileFailure.ofInternalError(
"Shared Library " + info.getName()
+ " is being installed twice in this set!",
PackageManagerException.INTERNAL_ERROR_SHARED_LIB_INSTALLED_TWICE);
}
}
}
}
final AndroidPackage systemPackage = allPackages.get(KnownPackages.SYSTEM_PACKAGE_NAME);
for (InstallRequest installRequest : installRequests) {
final String installPackageName = installRequest.getParsedPackage().getPackageName();
final List<SharedLibraryInfo> allowedSharedLibInfos =
sharedLibraries.getAllowedSharedLibInfos(installRequest);
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
final int deleteFlags = PackageManager.DELETE_KEEP_DATA
| (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(
installRequest.getRemovedInfo(),
installRequest.getOriginalPackageSetting(),
installRequest.getDisabledPackageSetting(),
deleteFlags, null /* all users */);
if (deletePackageAction == null) {
throw new ReconcileFailure(
PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
"May not delete " + installPackageName + " to replace");
}
} else {
deletePackageAction = null;
}
final int scanFlags = installRequest.getScanFlags();
final int parseFlags = installRequest.getParseFlags();
final ParsedPackage parsedPackage = installRequest.getParsedPackage();
final PackageSetting disabledPkgSetting = installRequest.getDisabledPackageSetting();
final PackageSetting lastStaticSharedLibSetting =
installRequest.getStaticSharedLibraryInfo() == null ? null
: sharedLibraries.getStaticSharedLibLatestVersionSetting(
installRequest);
final PackageSetting signatureCheckPs =
lastStaticSharedLibSetting != null
? lastStaticSharedLibSetting
: installRequest.getScannedPackageSetting();
boolean removeAppKeySetData = false;
boolean sharedUserSignaturesChanged = false;
SigningDetails signingDetails = null;
if (parsedPackage != null) {
signingDetails = parsedPackage.getSigningDetails();
}
final boolean isSystemPackage =
((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0);
final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr(
signatureCheckPs);
if (ksms.shouldCheckUpgradeKeySetLocked(
signatureCheckPs, sharedUserSetting, scanFlags)) {
if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
} else {
if (!isSystemPackage) {
throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + parsedPackage.getPackageName()
+ " upgrade keys do not match the previously installed"
+ " version");
} else {
String msg = "System package " + parsedPackage.getPackageName()
+ " signature changed; retaining data.";
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
}
}
} else {
try {
final Settings.VersionInfo versionInfo = versionInfos.get(installPackageName);
final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
final boolean isRollback = installRequest.isRollback();
final boolean compatMatch =
PackageManagerServiceUtils.verifySignatures(signatureCheckPs,
sharedUserSetting, disabledPkgSetting,
signingDetails, compareCompat,
compareRecover, isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
removeAppKeySetData = true;
}
if (!isSystemPackage && !isApex && signingDetails != null
&& systemPackage != null && systemPackage.getSigningDetails() != null
&& systemPackage.getSigningDetails().checkCapability(
signingDetails,
SigningDetails.CertCapabilities.PERMISSION)) {
Slog.d(TAG, "Non-preload app associated with system signature: "
+ signatureCheckPs.getPackageName());
if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) {
throw new ReconcileFailure(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
"Non-preload app associated with system signature: "
+ signatureCheckPs.getPackageName());
}
}
// if this is is a sharedUser, check to see if the new package is signed by a
// newer signing certificate than the existing one, and if so, copy over the new
// details
if (sharedUserSetting != null) {
// Attempt to merge the existing lineage for the shared SigningDetails with
// the lineage of the new package; if the shared SigningDetails are not
// returned this indicates the new package added new signers to the lineage
// and/or changed the capabilities of existing signers in the lineage.
SigningDetails sharedSigningDetails =
sharedUserSetting.signatures.mSigningDetails;
SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
signingDetails);
if (mergedDetails != sharedSigningDetails) {
// Use the restricted merge rule with the signing lineages from the
// other packages in the sharedUserId to ensure if any revoke a
// capability from a previous signer then this is reflected in the
// shared lineage.
for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) {
if (androidPackage.getPackageName() != null
&& !androidPackage.getPackageName().equals(
parsedPackage.getPackageName())) {
mergedDetails = mergedDetails.mergeLineageWith(
androidPackage.getSigningDetails(),
MERGE_RESTRICTED_CAPABILITY);
}
}
sharedUserSetting.signatures.mSigningDetails =
mergedDetails;
}
if (sharedUserSetting.signaturesChanged == null) {
sharedUserSetting.signaturesChanged = Boolean.FALSE;
}
}
} catch (PackageManagerException e) {
if (!isSystemPackage) {
throw new ReconcileFailure(e);
}
signingDetails = parsedPackage.getSigningDetails();
// If the system app is part of a shared user we allow that shared user to
// change
// signatures as well as part of an OTA. We still need to verify that the
// signatures
// are consistent within the shared user for a given boot, so only allow
// updating
// the signatures on the first package scanned for the shared user (i.e. if the
// signaturesChanged state hasn't been initialized yet in SharedUserSetting).
if (sharedUserSetting != null) {
if (sharedUserSetting.signaturesChanged != null
&& !PackageManagerServiceUtils.canJoinSharedUserId(
parsedPackage.getPackageName(), parsedPackage.getSigningDetails(),
sharedUserSetting,
PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) {
if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
// Mismatched signatures is an error and silently skipping system
// packages will likely break the device in unforeseen ways.
// However, we allow the device to boot anyway because, prior to Q,
// vendors were not expecting the platform to crash in this
// situation.
// This WILL be a hard failure on any new API levels after Q.
throw new ReconcileFailure(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
"Signature mismatch for shared user: "
+ sharedUserSetting);
} else {
// Treat mismatched signatures on system packages using a shared
// UID as
// fatal for the system overall, rather than just failing to install
// whichever package happened to be scanned later.
throw new IllegalStateException(
"Signature mismatch on system package "
+ parsedPackage.getPackageName()
+ " for shared user "
+ sharedUserSetting);
}
}
sharedUserSignaturesChanged = true;
sharedUserSetting.signatures.mSigningDetails =
parsedPackage.getSigningDetails();
sharedUserSetting.signaturesChanged = Boolean.TRUE;
}
// File a report about this.
String msg = "System package " + parsedPackage.getPackageName()
+ " signature changed; retaining data.";
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
} catch (IllegalArgumentException e) {
// should never happen: certs matched when checking, but not when comparing
// old to new for sharedUser
throw new RuntimeException(
"Signing certificates comparison made on incomparable signing details"
+ " but somehow passed verifySignatures!", e);
}
}
final ReconciledPackage reconciledPackage =
new ReconciledPackage(installRequests, allPackages, installRequest,
deletePackageAction, allowedSharedLibInfos, signingDetails,
sharedUserSignaturesChanged, removeAppKeySetData);
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
// will take care of the system apps by updating all of their
// library paths after the scan is done. Also during the initial
// scan don't update any libs as we do this wholesale after all
// apps are scanned to avoid dependency based scanning.
if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0
&& (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
== 0) {
try {
reconciledPackage.mCollectedSharedLibraryInfos =
sharedLibraries.collectSharedLibraryInfos(
installRequest.getParsedPackage(), combinedPackages,
incomingSharedLibraries);
} catch (PackageManagerException e) {
throw new ReconcileFailure(e.error, e.getMessage());
}
}
installRequest.onReconcileFinished();
result.add(reconciledPackage);
}
return result;
}
/**
* If the database version for this type of package (internal storage or
* external storage) is less than the version where package signatures
* were updated, return true.
*/
public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
}
public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
}
}