blob: 7d2aa535d5b9aa94d4db793dc644928f7da53a1d [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 com.android.server.pm.PackageManagerService.SCAN_BOOTING;
import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.parsing.ParsingPackageUtils;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.utils.WatchedLongSparseArray;
import java.util.List;
import java.util.Map;
final class ReconcilePackageUtils {
public static Map<String, ReconciledPackage> reconcilePackages(
final ReconcileRequest request, KeySetManagerService ksms,
PackageManagerServiceInjector injector)
throws ReconcileFailure {
final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.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<>(request.mAllPackages.size() + scannedPackages.size());
combinedPackages.putAll(request.mAllPackages);
final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
new ArrayMap<>();
for (String installPackageName : scannedPackages.keySet()) {
final ScanResult scanResult = scannedPackages.get(installPackageName);
// add / replace existing with incoming packages
combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
scanResult.mRequest.mParsedPackage);
// in the first pass, we'll build up the set of incoming shared libraries
final List<SharedLibraryInfo> allowedSharedLibInfos =
SharedLibraryUtils.getAllowedSharedLibInfos(scanResult,
request.mSharedLibrarySource);
if (allowedSharedLibInfos != null) {
for (SharedLibraryInfo info : allowedSharedLibInfos) {
if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
incomingSharedLibraries, info)) {
throw new ReconcileFailure("Shared Library " + info.getName()
+ " is being installed twice in this set!");
}
}
}
// the following may be null if we're just reconciling on boot (and not during install)
final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
final boolean isInstall = installArgs != null;
if (isInstall && (res == null || prepareResult == null)) {
throw new ReconcileFailure("Reconcile arguments are not balanced for "
+ installPackageName + "!");
}
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
final int deleteFlags = PackageManager.DELETE_KEEP_DATA
| (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
prepareResult.mOriginalPs, prepareResult.mDisabledPs,
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 = scanResult.mRequest.mScanFlags;
final int parseFlags = scanResult.mRequest.mParseFlags;
final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
final PackageSetting lastStaticSharedLibSetting =
request.mLastStaticSharedLibSettings.get(installPackageName);
final PackageSetting signatureCheckPs =
(prepareResult != null && lastStaticSharedLibSetting != null)
? lastStaticSharedLibSetting
: scanResult.mPkgSetting;
boolean removeAppKeySetData = false;
boolean sharedUserSignaturesChanged = false;
SigningDetails signingDetails = null;
if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
} else {
if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
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);
}
}
signingDetails = parsedPackage.getSigningDetails();
} else {
try {
final Settings.VersionInfo versionInfo =
request.mVersionInfos.get(installPackageName);
final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
final boolean isRollback = installArgs != null
&& installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
final boolean compatMatch = verifySignatures(signatureCheckPs,
disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
compareRecover, isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
removeAppKeySetData = true;
}
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
signingDetails = parsedPackage.getSigningDetails();
// 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 (signatureCheckPs.getSharedUser() != 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 =
signatureCheckPs.getSharedUser().signatures.mSigningDetails;
SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
signingDetails);
if (mergedDetails != sharedSigningDetails) {
signatureCheckPs.getSharedUser().signatures.mSigningDetails =
mergedDetails;
}
if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
}
}
} catch (PackageManagerException e) {
if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
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 (signatureCheckPs.getSharedUser() != null) {
final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
.signatures.mSigningDetails.getSignatures();
if (signatureCheckPs.getSharedUser().signaturesChanged != null
&& compareSignatures(sharedUserSignatures,
parsedPackage.getSigningDetails().getSignatures())
!= PackageManager.SIGNATURE_MATCH) {
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: "
+ scanResult.mPkgSetting.getSharedUser());
} 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 "
+ scanResult.mPkgSetting.getSharedUser());
}
}
sharedUserSignaturesChanged = true;
signatureCheckPs.getSharedUser().signatures.mSigningDetails =
parsedPackage.getSigningDetails();
signatureCheckPs.getSharedUser().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);
}
}
result.put(installPackageName,
new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
res, request.mPreparedPackages.get(installPackageName), scanResult,
deletePackageAction, allowedSharedLibInfos, signingDetails,
sharedUserSignaturesChanged, removeAppKeySetData));
}
for (String installPackageName : scannedPackages.keySet()) {
// 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.
final ScanResult scanResult = scannedPackages.get(installPackageName);
if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
|| (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
!= 0) {
continue;
}
try {
result.get(installPackageName).mCollectedSharedLibraryInfos =
SharedLibraryUtils.collectSharedLibraryInfos(
scanResult.mRequest.mParsedPackage,
combinedPackages, request.mSharedLibrarySource,
incomingSharedLibraries, injector.getCompatibility());
} catch (PackageManagerException e) {
throw new ReconcileFailure(e.error, e.getMessage());
}
}
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;
}
}