diff --git a/core/api/current.txt b/core/api/current.txt index 1951d99b718d6..0b09c08fcc242 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12050,7 +12050,6 @@ package android.content.pm { public static class PackageInstaller.Session implements java.io.Closeable { method public void abandon(); - method @Deprecated public void addChecksums(@NonNull String, @NonNull java.util.List) throws java.io.IOException; method public void addChildSessionId(int); method public void close(); method public void commit(@NonNull android.content.IntentSender); @@ -12064,6 +12063,7 @@ package android.content.pm { method @NonNull public java.io.OutputStream openWrite(@NonNull String, long, long) throws java.io.IOException; method public void removeChildSessionId(int); method public void removeSplit(@NonNull String) throws java.io.IOException; + method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List, @Nullable byte[]) throws java.io.IOException; method public void setStagingProgress(float); method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index 9a73be9d44a44..f72288c670d92 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -35,7 +35,7 @@ interface IPackageInstallerSession { void write(String name, long offsetBytes, long lengthBytes, in ParcelFileDescriptor fd); void stageViaHardLink(String target); - void addChecksums(String name, in Checksum[] checksums); + void setChecksums(String name, in Checksum[] checksums, in byte[] signature); void removeSplit(String splitName); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 01d4c2801b24c..248be0f095107 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1247,12 +1247,15 @@ public class PackageInstaller { } /** - * Adds installer-provided checksums for the APK file in session. + * Sets installer-provided checksums for the APK file in session. * * @param name previously written as part of this session. * {@link #openWrite} * @param checksums installer intends to make available via * {@link PackageManager#requestChecksums}. + * @param signature PKCS#7 detached signature bytes over serialized checksums to enable + * fs-verity for the checksums or null if fs-verity should not be enabled. + * @see fs-verity * @throws SecurityException if called after the session has been * committed or abandoned. * @throws IllegalStateException if checksums for this file have already been added. @@ -1262,13 +1265,14 @@ public class PackageInstaller { * in {@link PackageManager#requestChecksums}. */ @Deprecated - public void addChecksums(@NonNull String name, @NonNull List checksums) - throws IOException { + public void setChecksums(@NonNull String name, @NonNull List checksums, + @Nullable byte[] signature) throws IOException { Objects.requireNonNull(name); Objects.requireNonNull(checksums); try { - mSession.addChecksums(name, checksums.toArray(new Checksum[checksums.size()])); + mSession.setChecksums(name, checksums.toArray(new Checksum[checksums.size()]), + signature); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index b65fc73584713..712d0d63ab290 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -186,6 +186,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { static final String TAG_CHILD_SESSION = "childSession"; static final String TAG_SESSION_FILE = "sessionFile"; static final String TAG_SESSION_CHECKSUM = "sessionChecksum"; + static final String TAG_SESSION_CHECKSUM_SIGNATURE = "sessionChecksumSignature"; private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission"; private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION = "whitelisted-restricted-permission"; @@ -397,8 +398,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private ArraySet mFiles = new ArraySet<>(); + static class PerFileChecksum { + private final Checksum[] mChecksums; + private final byte[] mSignature; + + PerFileChecksum(Checksum[] checksums, byte[] signature) { + mChecksums = checksums; + mSignature = signature; + } + + Checksum[] getChecksums() { + return this.mChecksums; + } + + byte[] getSignature() { + return this.mSignature; + } + } + @GuardedBy("mLock") - private ArrayMap mChecksums = new ArrayMap<>(); + private ArrayMap mChecksums = new ArrayMap<>(); @Nullable final StagedSession mStagedSession; @@ -921,7 +940,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { int sessionId, int userId, int installerUid, @NonNull InstallSource installSource, SessionParams params, long createdMillis, long committedMillis, File stageDir, String stageCid, InstallationFile[] files, - ArrayMap> checksums, + ArrayMap checksums, boolean prepared, boolean committed, boolean destroyed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, @@ -967,11 +986,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } if (checksums != null) { - for (int i = 0, isize = checksums.size(); i < isize; ++i) { - final String fileName = checksums.keyAt(i); - final List fileChecksums = checksums.valueAt(i); - mChecksums.put(fileName, fileChecksums.toArray(new Checksum[fileChecksums.size()])); - } + mChecksums.putAll(checksums); } if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) { @@ -1253,7 +1268,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override - public void addChecksums(String name, @NonNull Checksum[] checksums) { + public void setChecksums(String name, @NonNull Checksum[] checksums, + @Nullable byte[] signature) { if (checksums.length == 0) { return; } @@ -1269,6 +1285,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IllegalStateException("Can't obtain calling installer's package."); } + if (signature != null && signature.length != 0) { + final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled(); + final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled(); + if (!standardMode || legacyMode) { + Slog.e(TAG, + "Can't enforce checksum's signature: Apk-Verity is disabled or in legacy " + + "mode."); + signature = null; + } + } + synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotCommittedOrDestroyedLocked("addChecksums"); @@ -1277,7 +1304,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IllegalStateException("Duplicate checksums."); } - mChecksums.put(name, checksums); + mChecksums.put(name, new PerFileChecksum(checksums, signature)); } } @@ -3032,15 +3059,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { maybeStageFsveritySignatureLocked(dexMetadataFile, targetDexMetadataFile); } + private void storeBytesToInstallationFile(final String localPath, final String absolutePath, + final byte[] bytes) throws IOException { + if (!isIncrementalInstallation() || mIncrementalFileStorages == null) { + FileUtils.bytesToFile(absolutePath, bytes); + } else { + mIncrementalFileStorages.makeFile(localPath, bytes); + } + } + @GuardedBy("mLock") private void maybeStageDigestsLocked(File origFile, File targetFile, String splitName) throws PackageManagerException { - final Checksum[] checksums = mChecksums.get(origFile.getName()); - if (checksums == null) { + final PerFileChecksum perFileChecksum = mChecksums.get(origFile.getName()); + if (perFileChecksum == null) { return; } mChecksums.remove(origFile.getName()); + final Checksum[] checksums = perFileChecksum.getChecksums(); if (checksums.length == 0) { return; } @@ -3048,14 +3085,24 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String targetDigestsPath = ApkChecksums.buildDigestsPathForApk(targetFile.getName()); final File targetDigestsFile = new File(stageDir, targetDigestsPath); try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + // Storing and staging checksums. ApkChecksums.writeChecksums(os, checksums); - final byte[] checksumsBytes = os.toByteArray(); + storeBytesToInstallationFile(targetDigestsPath, targetDigestsFile.getAbsolutePath(), + os.toByteArray()); + stageFileLocked(targetDigestsFile, targetDigestsFile); - if (!isIncrementalInstallation() || mIncrementalFileStorages == null) { - FileUtils.bytesToFile(targetDigestsFile.getAbsolutePath(), checksumsBytes); - } else { - mIncrementalFileStorages.makeFile(targetDigestsPath, checksumsBytes); + final byte[] signature = perFileChecksum.getSignature(); + if (signature == null || signature.length == 0) { + return; } + + // Storing and staging signature. + final String targetDigestsSignaturePath = VerityUtils.getFsveritySignatureFilePath( + targetDigestsPath); + final File targetDigestsSignatureFile = new File(stageDir, targetDigestsSignaturePath); + storeBytesToInstallationFile(targetDigestsSignaturePath, + targetDigestsSignatureFile.getAbsolutePath(), signature); + stageFileLocked(targetDigestsSignatureFile, targetDigestsSignatureFile); } catch (CertificateException e) { throw new PackageManagerException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to encode certificate for " + mPackageName, e); @@ -3063,8 +3110,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed to store digests for " + mPackageName, e); } - - stageFileLocked(targetDigestsFile, targetDigestsFile); } @GuardedBy("mLock") @@ -4277,7 +4322,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { for (int i = 0, isize = mChecksums.size(); i < isize; ++i) { final String fileName = mChecksums.keyAt(i); - final Checksum[] checksums = mChecksums.valueAt(i); + final PerFileChecksum perFileChecksum = mChecksums.valueAt(i); + final Checksum[] checksums = perFileChecksum.getChecksums(); for (Checksum checksum : checksums) { out.startTag(null, TAG_SESSION_CHECKSUM); writeStringAttribute(out, ATTR_NAME, fileName); @@ -4286,6 +4332,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { out.endTag(null, TAG_SESSION_CHECKSUM); } } + for (int i = 0, isize = mChecksums.size(); i < isize; ++i) { + final String fileName = mChecksums.keyAt(i); + final PerFileChecksum perFileChecksum = mChecksums.valueAt(i); + final byte[] signature = perFileChecksum.getSignature(); + if (signature == null || signature.length == 0) { + continue; + } + out.startTag(null, TAG_SESSION_CHECKSUM_SIGNATURE); + writeStringAttribute(out, ATTR_NAME, fileName); + writeByteArrayAttribute(out, ATTR_SIGNATURE, signature); + out.endTag(null, TAG_SESSION_CHECKSUM_SIGNATURE); + } + } out.endTag(null, TAG_SESSION); @@ -4401,6 +4460,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { List childSessionIds = new ArrayList<>(); List files = new ArrayList<>(); ArrayMap> checksums = new ArrayMap<>(); + ArrayMap signatures = new ArrayMap<>(); int outerDepth = in.getDepth(); int type; while ((type = in.next()) != XmlPullParser.END_DOCUMENT @@ -4443,6 +4503,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } fileChecksums.add(checksum); } + if (TAG_SESSION_CHECKSUM_SIGNATURE.equals(in.getName())) { + final String fileName = readStringAttribute(in, ATTR_NAME); + final byte[] signature = readByteArrayAttribute(in, ATTR_SIGNATURE); + signatures.put(fileName, signature); + } } if (grantedRuntimePermissions.size() > 0) { @@ -4471,13 +4536,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { fileArray = files.toArray(EMPTY_INSTALLATION_FILE_ARRAY); } + ArrayMap checksumsMap = null; + if (!checksums.isEmpty()) { + checksumsMap = new ArrayMap<>(checksums.size()); + for (int i = 0, isize = checksums.size(); i < isize; ++i) { + final String fileName = checksums.keyAt(i); + final List perFileChecksum = checksums.valueAt(i); + final byte[] perFileSignature = signatures.get(fileName); + checksumsMap.put(fileName, new PerFileChecksum( + perFileChecksum.toArray(new Checksum[perFileChecksum.size()]), + perFileSignature)); + } + } + InstallSource installSource = InstallSource.create(installInitiatingPackageName, installOriginatingPackageName, installerPackageName, installerAttributionTag); - return new PackageInstallerSession(callback, context, pm, sessionProvider, - installerThread, stagingManager, sessionId, userId, installerUid, - installSource, params, createdMillis, committedMillis, stageDir, stageCid, - fileArray, checksums, prepared, committed, destroyed, sealed, childSessionIdsArray, - parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode, - stagedSessionErrorMessage); + return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, + stagingManager, sessionId, userId, installerUid, installSource, params, + createdMillis, committedMillis, stageDir, stageCid, fileArray, checksumsMap, + prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId, + isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage); } }