Loading core/java/android/content/pm/PackageParser.java +57 −73 Original line number Diff line number Diff line Loading @@ -619,21 +619,6 @@ public class PackageParser { return path.endsWith(APK_FILE_EXTENSION); } /** * Generate and return the {@link PackageInfo} for a parsed package. * * @param p the parsed package. * @param flags indicating which optional information is included. */ @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state) { return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, UserHandle.getCallingUserId()); } /** * Returns true if the package is installed and not hidden, or if the caller * explicitly wanted all uninstalled and hidden packages as well. Loading @@ -660,8 +645,45 @@ public class PackageParser { return checkUseInstalledOrHidden(0, state, null); } /** * Generate and return the {@link PackageInfo} for a parsed package. * * @param p the parsed package. * @param flags indicating which optional information is included. */ @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state) { return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, UserHandle.getCallingUserId()); } @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId) { return generatePackageInfo(p, null, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, userId); } /** * PackageInfo generator specifically for apex files. * * @param pkg Package to generate info from. Should be derived from an apex. * @param apexInfo Apex info relating to the package. * @return PackageInfo * @throws PackageParserException */ public static PackageInfo generatePackageInfo( PackageParser.Package pkg, ApexInfo apexInfo, int flags) { return generatePackageInfo(pkg, apexInfo, EmptyArray.INT, flags, 0, 0, Collections.emptySet(), new PackageUserState(), UserHandle.getCallingUserId()); } private static PackageInfo generatePackageInfo(PackageParser.Package p, ApexInfo apexInfo, int gids[], int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) { Loading Loading @@ -808,6 +830,25 @@ public class PackageParser { } } } if (apexInfo != null) { File apexFile = new File(apexInfo.modulePath); pi.applicationInfo.sourceDir = apexFile.getPath(); pi.applicationInfo.publicSourceDir = apexFile.getPath(); if (apexInfo.isFactory) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; } if (apexInfo.isActive) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } pi.isApex = true; } // deprecated method of getting signing certificates if ((flags & PackageManager.GET_SIGNATURES) != 0) { if (p.mSigningDetails.hasPastSigningCertificates()) { Loading Loading @@ -8397,61 +8438,4 @@ public class PackageParser { } } // TODO(b/129261524): Clean up API /** * PackageInfo parser specifically for apex files. * NOTE: It will collect certificates * * @param apexInfo * @return PackageInfo * @throws PackageParserException */ public static PackageInfo generatePackageInfoFromApex(ApexInfo apexInfo, int flags) throws PackageParserException { PackageParser pp = new PackageParser(); File apexFile = new File(apexInfo.modulePath); final Package p = pp.parsePackage(apexFile, flags, false); PackageUserState state = new PackageUserState(); PackageInfo pi = generatePackageInfo(p, EmptyArray.INT, flags, 0, 0, Collections.emptySet(), state); pi.applicationInfo.sourceDir = apexFile.getPath(); pi.applicationInfo.publicSourceDir = apexFile.getPath(); if (apexInfo.isFactory) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; } if (apexInfo.isActive) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } pi.isApex = true; // Collect certificates if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) { collectCertificates(p, apexFile, false); // Keep legacy mechanism for handling signatures. While this is deprecated, it's // still part of the public API and needs to be maintained if (p.mSigningDetails.hasPastSigningCertificates()) { // Package has included signing certificate rotation information. Return // the oldest cert so that programmatic checks keep working even if unaware // of key rotation. pi.signatures = new Signature[1]; pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0]; } else if (p.mSigningDetails.hasSignatures()) { // otherwise keep old behavior int numberOfSigs = p.mSigningDetails.signatures.length; pi.signatures = new Signature[numberOfSigs]; System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs); } if (p.mSigningDetails != SigningDetails.UNKNOWN) { // only return a valid SigningInfo if there is signing information to report pi.signingInfo = new SigningInfo(p.mSigningDetails); } else { pi.signingInfo = null; } } return pi; } } core/tests/coretests/src/android/content/pm/PackageParserTest.java +6 −1 Original line number Diff line number Diff line Loading @@ -508,7 +508,12 @@ public class PackageParserTest { apexInfo.modulePath = apexFile.getPath(); apexInfo.versionCode = 191000070; int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES; PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexInfo, flags); PackageParser pp = new PackageParser(); Package p = pp.parsePackage(apexFile, flags, false); PackageParser.collectCertificates(p, false); PackageInfo pi = PackageParser.generatePackageInfo(p, apexInfo, flags); assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertTrue(pi.applicationInfo.enabled); assertEquals(28, pi.applicationInfo.targetSdkVersion); Loading services/core/java/com/android/server/pm/ApexManager.java +437 −334 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/pm/PackageManagerService.java +2 −1 Original line number Diff line number Diff line Loading @@ -2490,7 +2490,8 @@ public class PackageManagerService extends IPackageManager.Stub mProtectedPackages = new ProtectedPackages(mContext); mApexManager = new ApexManager(context); mApexManager = ApexManager.create(context); // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mPackages) { services/core/java/com/android/server/pm/StagingManager.java +118 −107 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; Loading @@ -43,6 +44,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.apk.ApkSignatureVerifier; Loading Loading @@ -104,21 +106,22 @@ public class StagingManager { return new ParceledListSlice<>(result); } private boolean validateApexSignature(String apexPath, String apexModuleName) { private void validateApexSignature(String apexPath, String packageName) throws PackageManagerException { final SigningDetails signingDetails; try { signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e); return false; throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexPath, e); } final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(apexModuleName); final PackageInfo packageInfo = mApexManager.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE); if (packageInfo == null) { // Don't allow installation of new APEX. Slog.e(TAG, "Attempted to install a new apex " + apexModuleName + ". Rejecting"); return false; // This should never happen, because submitSessionToApexService ensures that no new // apexes were installed. throw new IllegalStateException("Unknown apex package " + packageName); } final SigningDetails existingSigningDetails; Loading @@ -126,73 +129,99 @@ public class StagingManager { existingSigningDetails = ApkSignatureVerifier.verify( packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { Slog.e(TAG, "Unable to parse APEX package: " + packageInfo.applicationInfo.sourceDir, e); return false; throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + packageInfo.applicationInfo.sourceDir, e); } // Now that we have both sets of signatures, demand that they're an exact match. if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) { return true; return; } return false; throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APK-container signature verification failed for package " + packageName + ". Signature of file " + apexPath + " does not match the signature of " + " the package already installed."); } private boolean submitSessionToApexService(@NonNull PackageInstallerSession session, List<PackageInstallerSession> childSessions, ApexInfoList apexInfoList) { boolean submittedToApexd = mApexManager.submitStagedSession( session.sessionId, childSessions != null ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() : new int[]{}, apexInfoList); if (!submittedToApexd) { session.setStagedSessionFailed( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APEX staging failed, check logcat messages from apexd for more details."); return false; private List<PackageInfo> submitSessionToApexService( @NonNull PackageInstallerSession session) throws PackageManagerException { final IntArray childSessionsIds = new IntArray(); if (session.isMultiPackage()) { for (int id : session.getChildSessionIds()) { if (isApexSession(mStagedSessions.get(id))) { childSessionsIds.add(id); } } } // submitStagedSession will throw a PackageManagerException if apexd verification fails, // which will be propagated to populate stagedSessionErrorMessage of this session. final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId, childSessionsIds.toArray()); final List<PackageInfo> result = new ArrayList<>(); for (ApexInfo apexInfo : apexInfoList.apexInfos) { final PackageInfo packageInfo; int flags = PackageManager.GET_META_DATA; PackageParser.Package pkg; try { File apexFile = new File(apexInfo.modulePath); PackageParser pp = new PackageParser(); pkg = pp.parsePackage(apexFile, flags, false); } catch (PackageParserException e) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexInfo.modulePath, e); } for (ApexInfo newModule : apexInfoList.apexInfos) { PackageInfo activePackage = mApexManager.getPackageInfoForApexName( newModule.moduleName); packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags); final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName, ApexManager.MATCH_ACTIVE_PACKAGE); if (activePackage == null) { continue; Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName); throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "It is forbidden to install new APEX packages."); } checkRequiredVersionCode(session, activePackage); checkDowngrade(session, activePackage, packageInfo); result.add(packageInfo); } return result; } private void checkRequiredVersionCode(final PackageInstallerSession session, final PackageInfo activePackage) throws PackageManagerException { if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) { return; } long activeVersion = activePackage.applicationInfo.longVersionCode; if (session.params.requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) { final long activeVersion = activePackage.applicationInfo.longVersionCode; if (activeVersion != session.params.requiredInstalledVersionCode) { session.setStagedSessionFailed( if (!mApexManager.abortActiveSession()) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } throw new PackageManagerException( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Installed version of APEX package " + activePackage.packageName + " does not match required. Active version: " + activeVersion + " required: " + session.params.requiredInstalledVersionCode); if (!mApexManager.abortActiveSession()) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } return false; } } private void checkDowngrade(final PackageInstallerSession session, final PackageInfo activePackage, final PackageInfo newPackage) throws PackageManagerException { final long activeVersion = activePackage.applicationInfo.longVersionCode; final long newVersionCode = newPackage.applicationInfo.longVersionCode; boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( session.params.installFlags, activePackage.applicationInfo.flags); if (activeVersion > newModule.versionCode && !allowsDowngrade) { session.setStagedSessionFailed( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Downgrade of APEX package " + activePackage.packageName + " is not allowed. Active version: " + activeVersion + " attempted: " + newModule.versionCode); if (activeVersion > newVersionCode && !allowsDowngrade) { if (!mApexManager.abortActiveSession()) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } return false; } throw new PackageManagerException( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Downgrade of APEX package " + newPackage.packageName + " is not allowed. Active version: " + activeVersion + " attempted: " + newVersionCode); } return true; } private static boolean isApexSession(@NonNull PackageInstallerSession session) { Loading @@ -200,32 +229,21 @@ public class StagingManager { } private void preRebootVerification(@NonNull PackageInstallerSession session) { boolean success = true; final ApexInfoList apexInfoList = new ApexInfoList(); final boolean hasApex = sessionContainsApex(session); // APEX checks. For single-package sessions, check if they contain an APEX. For // multi-package sessions, find all the child sessions that contain an APEX. if (!session.isMultiPackage() && isApexSession(session)) { success = submitSessionToApexService(session, null, apexInfoList); } else if (session.isMultiPackage()) { List<PackageInstallerSession> childSessions = Arrays.stream(session.getChildSessionIds()) // Retrieve cached sessions matching ids. .mapToObj(i -> mStagedSessions.get(i)) // Filter only the ones containing APEX. .filter(childSession -> isApexSession(childSession)) .collect(Collectors.toList()); if (!childSessions.isEmpty()) { success = submitSessionToApexService(session, childSessions, apexInfoList); } // else this is a staged multi-package session with no APEX files. if (hasApex) { try { final List<PackageInfo> apexPackages = submitSessionToApexService(session); for (PackageInfo apexPackage : apexPackages) { validateApexSignature(apexPackage.applicationInfo.sourceDir, apexPackage.packageName); } if (!success) { // submitSessionToApexService will populate error. } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); return; } } if (sessionContainsApk(session)) { if (!installApksInSession(session, /* preReboot */ true)) { Loading @@ -237,25 +255,6 @@ public class StagingManager { } } if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) { // For APEXes, we validate the signature here before we mark the session as ready, // so we fail the session early if there is a signature mismatch. For APKs, the // signature verification will be done by the package manager at the point at which // it applies the staged install. for (ApexInfo apexModule : apexInfoList.apexInfos) { if (!validateApexSignature(apexModule.modulePath, apexModule.moduleName)) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APK-container signature verification failed for package " + apexModule.moduleName + ". Signature of file " + apexModule.modulePath + " does not match the signature of " + " the package already installed."); // TODO(b/118865310): abort the session on apexd. return; } } } if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { // If rollback is enabled for this session, we call through to the RollbackManager // with the list of sessions it must enable rollback for. Note that notifyStagedSession Loading @@ -273,12 +272,24 @@ public class StagingManager { } } // Proactively mark session as ready before calling apexd. Although this call order looks // counter-intuitive, this is the easiest way to ensure that session won't end up in the // inconsistent state: // - If device gets rebooted right before call to apexd, then apexd will never activate // apex files of this staged session. This will result in StagingManager failing the // session. // On the other hand, if the order of the calls was inverted (first call apexd, then mark // session as ready), then if a device gets rebooted right after the call to apexd, only // apex part of the train will be applied, leaving device in an inconsistent state. session.setStagedSessionReady(); if (sessionContainsApex(session) && !mApexManager.markStagedSessionReady(session.sessionId)) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APEX staging failed, check logcat messages from apexd for more " + "details."); if (!hasApex) { // Session doesn't contain apex, nothing to do. return; } try { mApexManager.markStagedSessionReady(session.sessionId); } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); } } Loading Loading
core/java/android/content/pm/PackageParser.java +57 −73 Original line number Diff line number Diff line Loading @@ -619,21 +619,6 @@ public class PackageParser { return path.endsWith(APK_FILE_EXTENSION); } /** * Generate and return the {@link PackageInfo} for a parsed package. * * @param p the parsed package. * @param flags indicating which optional information is included. */ @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state) { return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, UserHandle.getCallingUserId()); } /** * Returns true if the package is installed and not hidden, or if the caller * explicitly wanted all uninstalled and hidden packages as well. Loading @@ -660,8 +645,45 @@ public class PackageParser { return checkUseInstalledOrHidden(0, state, null); } /** * Generate and return the {@link PackageInfo} for a parsed package. * * @param p the parsed package. * @param flags indicating which optional information is included. */ @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state) { return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, UserHandle.getCallingUserId()); } @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int[] gids, int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId) { return generatePackageInfo(p, null, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, userId); } /** * PackageInfo generator specifically for apex files. * * @param pkg Package to generate info from. Should be derived from an apex. * @param apexInfo Apex info relating to the package. * @return PackageInfo * @throws PackageParserException */ public static PackageInfo generatePackageInfo( PackageParser.Package pkg, ApexInfo apexInfo, int flags) { return generatePackageInfo(pkg, apexInfo, EmptyArray.INT, flags, 0, 0, Collections.emptySet(), new PackageUserState(), UserHandle.getCallingUserId()); } private static PackageInfo generatePackageInfo(PackageParser.Package p, ApexInfo apexInfo, int gids[], int flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) { Loading Loading @@ -808,6 +830,25 @@ public class PackageParser { } } } if (apexInfo != null) { File apexFile = new File(apexInfo.modulePath); pi.applicationInfo.sourceDir = apexFile.getPath(); pi.applicationInfo.publicSourceDir = apexFile.getPath(); if (apexInfo.isFactory) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; } if (apexInfo.isActive) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } pi.isApex = true; } // deprecated method of getting signing certificates if ((flags & PackageManager.GET_SIGNATURES) != 0) { if (p.mSigningDetails.hasPastSigningCertificates()) { Loading Loading @@ -8397,61 +8438,4 @@ public class PackageParser { } } // TODO(b/129261524): Clean up API /** * PackageInfo parser specifically for apex files. * NOTE: It will collect certificates * * @param apexInfo * @return PackageInfo * @throws PackageParserException */ public static PackageInfo generatePackageInfoFromApex(ApexInfo apexInfo, int flags) throws PackageParserException { PackageParser pp = new PackageParser(); File apexFile = new File(apexInfo.modulePath); final Package p = pp.parsePackage(apexFile, flags, false); PackageUserState state = new PackageUserState(); PackageInfo pi = generatePackageInfo(p, EmptyArray.INT, flags, 0, 0, Collections.emptySet(), state); pi.applicationInfo.sourceDir = apexFile.getPath(); pi.applicationInfo.publicSourceDir = apexFile.getPath(); if (apexInfo.isFactory) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; } if (apexInfo.isActive) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } pi.isApex = true; // Collect certificates if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) { collectCertificates(p, apexFile, false); // Keep legacy mechanism for handling signatures. While this is deprecated, it's // still part of the public API and needs to be maintained if (p.mSigningDetails.hasPastSigningCertificates()) { // Package has included signing certificate rotation information. Return // the oldest cert so that programmatic checks keep working even if unaware // of key rotation. pi.signatures = new Signature[1]; pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0]; } else if (p.mSigningDetails.hasSignatures()) { // otherwise keep old behavior int numberOfSigs = p.mSigningDetails.signatures.length; pi.signatures = new Signature[numberOfSigs]; System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs); } if (p.mSigningDetails != SigningDetails.UNKNOWN) { // only return a valid SigningInfo if there is signing information to report pi.signingInfo = new SigningInfo(p.mSigningDetails); } else { pi.signingInfo = null; } } return pi; } }
core/tests/coretests/src/android/content/pm/PackageParserTest.java +6 −1 Original line number Diff line number Diff line Loading @@ -508,7 +508,12 @@ public class PackageParserTest { apexInfo.modulePath = apexFile.getPath(); apexInfo.versionCode = 191000070; int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES; PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexInfo, flags); PackageParser pp = new PackageParser(); Package p = pp.parsePackage(apexFile, flags, false); PackageParser.collectCertificates(p, false); PackageInfo pi = PackageParser.generatePackageInfo(p, apexInfo, flags); assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName); assertTrue(pi.applicationInfo.enabled); assertEquals(28, pi.applicationInfo.targetSdkVersion); Loading
services/core/java/com/android/server/pm/ApexManager.java +437 −334 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/pm/PackageManagerService.java +2 −1 Original line number Diff line number Diff line Loading @@ -2490,7 +2490,8 @@ public class PackageManagerService extends IPackageManager.Stub mProtectedPackages = new ProtectedPackages(mContext); mApexManager = new ApexManager(context); mApexManager = ApexManager.create(context); // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mPackages) {
services/core/java/com/android/server/pm/StagingManager.java +118 −107 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; Loading @@ -43,6 +44,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.apk.ApkSignatureVerifier; Loading Loading @@ -104,21 +106,22 @@ public class StagingManager { return new ParceledListSlice<>(result); } private boolean validateApexSignature(String apexPath, String apexModuleName) { private void validateApexSignature(String apexPath, String packageName) throws PackageManagerException { final SigningDetails signingDetails; try { signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e); return false; throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexPath, e); } final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(apexModuleName); final PackageInfo packageInfo = mApexManager.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE); if (packageInfo == null) { // Don't allow installation of new APEX. Slog.e(TAG, "Attempted to install a new apex " + apexModuleName + ". Rejecting"); return false; // This should never happen, because submitSessionToApexService ensures that no new // apexes were installed. throw new IllegalStateException("Unknown apex package " + packageName); } final SigningDetails existingSigningDetails; Loading @@ -126,73 +129,99 @@ public class StagingManager { existingSigningDetails = ApkSignatureVerifier.verify( packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR); } catch (PackageParserException e) { Slog.e(TAG, "Unable to parse APEX package: " + packageInfo.applicationInfo.sourceDir, e); return false; throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + packageInfo.applicationInfo.sourceDir, e); } // Now that we have both sets of signatures, demand that they're an exact match. if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) { return true; return; } return false; throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APK-container signature verification failed for package " + packageName + ". Signature of file " + apexPath + " does not match the signature of " + " the package already installed."); } private boolean submitSessionToApexService(@NonNull PackageInstallerSession session, List<PackageInstallerSession> childSessions, ApexInfoList apexInfoList) { boolean submittedToApexd = mApexManager.submitStagedSession( session.sessionId, childSessions != null ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() : new int[]{}, apexInfoList); if (!submittedToApexd) { session.setStagedSessionFailed( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APEX staging failed, check logcat messages from apexd for more details."); return false; private List<PackageInfo> submitSessionToApexService( @NonNull PackageInstallerSession session) throws PackageManagerException { final IntArray childSessionsIds = new IntArray(); if (session.isMultiPackage()) { for (int id : session.getChildSessionIds()) { if (isApexSession(mStagedSessions.get(id))) { childSessionsIds.add(id); } } } // submitStagedSession will throw a PackageManagerException if apexd verification fails, // which will be propagated to populate stagedSessionErrorMessage of this session. final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId, childSessionsIds.toArray()); final List<PackageInfo> result = new ArrayList<>(); for (ApexInfo apexInfo : apexInfoList.apexInfos) { final PackageInfo packageInfo; int flags = PackageManager.GET_META_DATA; PackageParser.Package pkg; try { File apexFile = new File(apexInfo.modulePath); PackageParser pp = new PackageParser(); pkg = pp.parsePackage(apexFile, flags, false); } catch (PackageParserException e) { throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Failed to parse APEX package " + apexInfo.modulePath, e); } for (ApexInfo newModule : apexInfoList.apexInfos) { PackageInfo activePackage = mApexManager.getPackageInfoForApexName( newModule.moduleName); packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags); final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName, ApexManager.MATCH_ACTIVE_PACKAGE); if (activePackage == null) { continue; Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName); throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "It is forbidden to install new APEX packages."); } checkRequiredVersionCode(session, activePackage); checkDowngrade(session, activePackage, packageInfo); result.add(packageInfo); } return result; } private void checkRequiredVersionCode(final PackageInstallerSession session, final PackageInfo activePackage) throws PackageManagerException { if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) { return; } long activeVersion = activePackage.applicationInfo.longVersionCode; if (session.params.requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) { final long activeVersion = activePackage.applicationInfo.longVersionCode; if (activeVersion != session.params.requiredInstalledVersionCode) { session.setStagedSessionFailed( if (!mApexManager.abortActiveSession()) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } throw new PackageManagerException( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Installed version of APEX package " + activePackage.packageName + " does not match required. Active version: " + activeVersion + " required: " + session.params.requiredInstalledVersionCode); if (!mApexManager.abortActiveSession()) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } return false; } } private void checkDowngrade(final PackageInstallerSession session, final PackageInfo activePackage, final PackageInfo newPackage) throws PackageManagerException { final long activeVersion = activePackage.applicationInfo.longVersionCode; final long newVersionCode = newPackage.applicationInfo.longVersionCode; boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted( session.params.installFlags, activePackage.applicationInfo.flags); if (activeVersion > newModule.versionCode && !allowsDowngrade) { session.setStagedSessionFailed( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Downgrade of APEX package " + activePackage.packageName + " is not allowed. Active version: " + activeVersion + " attempted: " + newModule.versionCode); if (activeVersion > newVersionCode && !allowsDowngrade) { if (!mApexManager.abortActiveSession()) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId); } return false; } throw new PackageManagerException( SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "Downgrade of APEX package " + newPackage.packageName + " is not allowed. Active version: " + activeVersion + " attempted: " + newVersionCode); } return true; } private static boolean isApexSession(@NonNull PackageInstallerSession session) { Loading @@ -200,32 +229,21 @@ public class StagingManager { } private void preRebootVerification(@NonNull PackageInstallerSession session) { boolean success = true; final ApexInfoList apexInfoList = new ApexInfoList(); final boolean hasApex = sessionContainsApex(session); // APEX checks. For single-package sessions, check if they contain an APEX. For // multi-package sessions, find all the child sessions that contain an APEX. if (!session.isMultiPackage() && isApexSession(session)) { success = submitSessionToApexService(session, null, apexInfoList); } else if (session.isMultiPackage()) { List<PackageInstallerSession> childSessions = Arrays.stream(session.getChildSessionIds()) // Retrieve cached sessions matching ids. .mapToObj(i -> mStagedSessions.get(i)) // Filter only the ones containing APEX. .filter(childSession -> isApexSession(childSession)) .collect(Collectors.toList()); if (!childSessions.isEmpty()) { success = submitSessionToApexService(session, childSessions, apexInfoList); } // else this is a staged multi-package session with no APEX files. if (hasApex) { try { final List<PackageInfo> apexPackages = submitSessionToApexService(session); for (PackageInfo apexPackage : apexPackages) { validateApexSignature(apexPackage.applicationInfo.sourceDir, apexPackage.packageName); } if (!success) { // submitSessionToApexService will populate error. } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); return; } } if (sessionContainsApk(session)) { if (!installApksInSession(session, /* preReboot */ true)) { Loading @@ -237,25 +255,6 @@ public class StagingManager { } } if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) { // For APEXes, we validate the signature here before we mark the session as ready, // so we fail the session early if there is a signature mismatch. For APKs, the // signature verification will be done by the package manager at the point at which // it applies the staged install. for (ApexInfo apexModule : apexInfoList.apexInfos) { if (!validateApexSignature(apexModule.modulePath, apexModule.moduleName)) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APK-container signature verification failed for package " + apexModule.moduleName + ". Signature of file " + apexModule.modulePath + " does not match the signature of " + " the package already installed."); // TODO(b/118865310): abort the session on apexd. return; } } } if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) { // If rollback is enabled for this session, we call through to the RollbackManager // with the list of sessions it must enable rollback for. Note that notifyStagedSession Loading @@ -273,12 +272,24 @@ public class StagingManager { } } // Proactively mark session as ready before calling apexd. Although this call order looks // counter-intuitive, this is the easiest way to ensure that session won't end up in the // inconsistent state: // - If device gets rebooted right before call to apexd, then apexd will never activate // apex files of this staged session. This will result in StagingManager failing the // session. // On the other hand, if the order of the calls was inverted (first call apexd, then mark // session as ready), then if a device gets rebooted right after the call to apexd, only // apex part of the train will be applied, leaving device in an inconsistent state. session.setStagedSessionReady(); if (sessionContainsApex(session) && !mApexManager.markStagedSessionReady(session.sessionId)) { session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, "APEX staging failed, check logcat messages from apexd for more " + "details."); if (!hasApex) { // Session doesn't contain apex, nothing to do. return; } try { mApexManager.markStagedSessionReady(session.sessionId); } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); } } Loading