diff options
6 files changed, 226 insertions, 86 deletions
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 74a4215b804f..35518db32829 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -50,6 +50,8 @@ import java.nio.file.Paths; public final class IncrementalManager { private static final String TAG = "IncrementalManager"; + private static final String ALLOWED_PROPERTY = "incremental.allowed"; + public static final int CREATE_MODE_TEMPORARY_BIND = IIncrementalService.CREATE_MODE_TEMPORARY_BIND; public static final int CREATE_MODE_PERMANENT_BIND = @@ -288,13 +290,21 @@ public final class IncrementalManager { } /** - * Checks if Incremental is enabled + * Checks if Incremental feature is enabled on this device. */ - public static boolean isEnabled() { + public static boolean isFeatureEnabled() { return nativeIsEnabled(); } /** + * Checks if Incremental installations are allowed. + * A developer can disable Incremental installations by setting the property. + */ + public static boolean isAllowed() { + return isFeatureEnabled() && android.os.SystemProperties.getBoolean(ALLOWED_PROPERTY, true); + } + + /** * Checks if path is mounted on Incremental File System. */ public static boolean isIncrementalPath(@NonNull String path) { diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 5fadee4355b3..6450a67572bf 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -16,8 +16,11 @@ package android.os.incremental; +import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; /** @@ -26,20 +29,36 @@ import java.io.IOException; * @hide */ public class V4Signature { + public static final String EXT = ".idsig"; + public final byte[] verityRootHash; public final byte[] v3Digest; public final byte[] pkcs7SignatureBlock; - V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) { - this.verityRootHash = verityRootHash; - this.v3Digest = v3Digest; - this.pkcs7SignatureBlock = pkcs7SignatureBlock; + /** + * Construct a V4Signature from .idsig file. + */ + public static V4Signature readFrom(File file) { + try (DataInputStream stream = new DataInputStream(new FileInputStream(file))) { + return readFrom(stream); + } catch (IOException e) { + return null; + } } - static byte[] readBytes(DataInputStream stream) throws IOException { - byte[] result = new byte[stream.readInt()]; - stream.read(result); - return result; + /** + * Store the V4Signature to a byte-array. + */ + public byte[] toByteArray() { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) { + this.writeTo(steam); + steam.flush(); + } + return byteArrayOutputStream.toByteArray(); + } catch (IOException e) { + return null; + } } static V4Signature readFrom(DataInputStream stream) throws IOException { @@ -49,9 +68,10 @@ public class V4Signature { return new V4Signature(verityRootHash, v3Digest, pkcs7SignatureBlock); } - static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException { - stream.writeInt(bytes.length); - stream.write(bytes); + V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) { + this.verityRootHash = verityRootHash; + this.v3Digest = v3Digest; + this.pkcs7SignatureBlock = pkcs7SignatureBlock; } void writeTo(DataOutputStream stream) throws IOException { @@ -59,4 +79,15 @@ public class V4Signature { writeBytes(stream, this.v3Digest); writeBytes(stream, this.pkcs7SignatureBlock); } + + private static byte[] readBytes(DataInputStream stream) throws IOException { + byte[] result = new byte[stream.readInt()]; + stream.read(result); + return result; + } + + private static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException { + stream.writeInt(bytes.length); + stream.write(bytes); + } } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 2fcc1de776fb..578c0cc23a74 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -1156,7 +1156,7 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_RAM_NORMAL, 0); } - if (IncrementalManager.isEnabled()) { + if (IncrementalManager.isFeatureEnabled()) { addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4ed4de70264a..0e554f8c3455 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -101,6 +101,7 @@ import android.os.RevocableFileDescriptor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.incremental.IncrementalFileStorages; +import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.provider.Settings.Secure; import android.stats.devicepolicy.DevicePolicyEnums; @@ -538,17 +539,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IllegalArgumentException( "DataLoader installation of APEX modules is not allowed."); } + if (this.params.dataLoaderParams.getComponentName().getPackageName() + == SYSTEM_DATA_LOADER_PACKAGE) { + assertShellOrSystemCalling("System data loaders"); + } } - if (isStreamingInstallation()) { + if (isIncrementalInstallation()) { + if (!IncrementalManager.isAllowed()) { + throw new IllegalArgumentException("Incremental installation not allowed."); + } if (!isIncrementalInstallationAllowed(mPackageName)) { throw new IllegalArgumentException( "Incremental installation of this package is not allowed."); } - if (this.params.dataLoaderParams.getComponentName().getPackageName() - == SYSTEM_DATA_LOADER_PACKAGE) { - assertShellOrSystemCalling("System data loaders"); - } } } @@ -2385,12 +2389,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); } - if (!isIncrementalInstallation()) { + if (isStreamingInstallation()) { if (location != LOCATION_DATA_APP) { throw new IllegalArgumentException( "Non-incremental installation only supports /data/app placement: " + name); } } + if (metadata == null) { + throw new IllegalArgumentException( + "DataLoader installation requires valid metadata: " + name); + } // Use installer provided name for now; we always rename later if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 3909fdff21f0..c267cea163d5 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -88,6 +88,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.incremental.V4Signature; import android.os.storage.StorageManager; import android.permission.IPermissionManager; import android.system.ErrnoException; @@ -3024,9 +3025,15 @@ class PackageManagerShellCommand extends ShellCommand { final File file = new File(inPath); final String name = file.getName(); final long size = file.length(); - byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8); + final byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8); - session.addFile(LOCATION_DATA_APP, name, size, metadata, null); + // Try to load a v4 signature for the APK. + final V4Signature v4signature = V4Signature.readFrom( + new File(inPath + V4Signature.EXT)); + final byte[] v4signatureBytes = + (v4signature != null) ? v4signature.toByteArray() : null; + + session.addFile(LOCATION_DATA_APP, name, size, metadata, v4signatureBytes); } return 0; } finally { diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index d803c1dc9397..7e6f79f0d407 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -20,6 +20,9 @@ #include <android-base/unique_fd.h> #include <nativehelper/JNIHelp.h> +#include "android-base/file.h" + +#include <endian.h> #include <core_jni_helpers.h> @@ -32,6 +35,8 @@ namespace android { namespace { +using android::base::borrowed_fd; +using android::base::ReadFully; using android::base::unique_fd; static constexpr int BUFFER_SIZE = 256 * 1024; @@ -80,6 +85,27 @@ const JniIds& jniIds(JNIEnv* env) { return ids; } +static inline int32_t readBEInt32(borrowed_fd fd) { + int32_t result; + ReadFully(fd, &result, sizeof(result)); + result = int32_t(be32toh(result)); + return result; +} + +static inline std::vector<char> readBytes(borrowed_fd fd) { + int32_t size = readBEInt32(fd); + std::vector<char> result(size); + ReadFully(fd, result.data(), size); + return result; +} + +static inline int32_t skipIdSigHeaders(borrowed_fd fd) { + readBytes(fd); // verityRootHash + readBytes(fd); // v3Digest + readBytes(fd); // pkcs7SignatureBlock + return readBEInt32(fd); // size of the verity tree +} + static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, jobject pfd) { if (!pfd) { ALOGE("Missing In ParcelFileDescriptor."); @@ -90,33 +116,78 @@ static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, job ALOGE("Missing In FileDescriptor."); return {}; } - return unique_fd{dup(jniGetFDFromFileDescriptor(env, managedFd))}; + unique_fd result{dup(jniGetFDFromFileDescriptor(env, managedFd))}; + // Can be closed after dup. + env->CallStaticVoidMethod(jni.ioUtils, jni.ioUtilsCloseQuietly, pfd); + return result; } -static inline std::pair<unique_fd, bool> openIncomingFile(JNIEnv* env, const JniIds& jni, - jobject shellCommand, - IncFsSpan metadata) { - jobject pfd = nullptr; - const bool stdin = (metadata.size == 0 || *metadata.data == '-'); - if (stdin) { +struct InputDesc { + unique_fd fd; + IncFsSize size; + IncFsBlockKind kind; + bool waitOnEof; +}; +using InputDescs = std::vector<InputDesc>; + +static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shellCommand, + IncFsSize size, IncFsSpan metadata) { + InputDescs result; + result.reserve(2); + + const bool fromStdin = (metadata.size == 0 || *metadata.data == '-'); + if (fromStdin) { // stdin - pfd = env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader, - jni.pmscdGetStdInPFD, shellCommand); - } else { - // file - const std::string filePath(metadata.data, metadata.size); - pfd = env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader, - jni.pmscdGetLocalFile, shellCommand, - env->NewStringUTF(filePath.c_str())); + auto fd = convertPfdToFdAndDup( + env, jni, + env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader, + jni.pmscdGetStdInPFD, shellCommand)); + if (fd.ok()) { + result.push_back(InputDesc{ + .fd = std::move(fd), + .size = size, + .kind = INCFS_BLOCK_KIND_DATA, + .waitOnEof = true, + }); + } + return result; } - auto result = convertPfdToFdAndDup(env, jni, pfd); - if (pfd) { - // Can be closed after dup. - env->CallStaticVoidMethod(jni.ioUtils, jni.ioUtilsCloseQuietly, pfd); + // local file and possibly signature + const std::string filePath(metadata.data, metadata.size); + const std::string idsigPath = filePath + ".idsig"; + + auto idsigFd = convertPfdToFdAndDup( + env, jni, + env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader, + jni.pmscdGetLocalFile, shellCommand, + env->NewStringUTF(idsigPath.c_str()))); + if (idsigFd.ok()) { + ALOGE("idsig found, skipping to the tree"); + auto treeSize = skipIdSigHeaders(idsigFd); + result.push_back(InputDesc{ + .fd = std::move(idsigFd), + .size = treeSize, + .kind = INCFS_BLOCK_KIND_HASH, + .waitOnEof = false, + }); } - return {std::move(result), stdin}; + auto fileFd = convertPfdToFdAndDup( + env, jni, + env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader, + jni.pmscdGetLocalFile, shellCommand, + env->NewStringUTF(filePath.c_str()))); + if (fileFd.ok()) { + result.push_back(InputDesc{ + .fd = std::move(fileFd), + .size = size, + .kind = INCFS_BLOCK_KIND_DATA, + .waitOnEof = false, + }); + } + + return result; } static inline JNIEnv* GetJNIEnvironment(JavaVM* vm) { @@ -187,9 +258,9 @@ private: blocks.reserve(BLOCKS_COUNT); for (auto&& file : addedFiles) { - auto [incomingFd, stdin] = openIncomingFile(env, jni, shellCommand, file.metadata); - if (incomingFd < 0) { - ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. " + auto inputs = openInputs(env, jni, shellCommand, file.size, file.metadata); + if (inputs.empty()) { + ALOGE("Failed to open an input file for metadata: %.*s, final file name is: %s. " "Error %d", int(file.metadata.size), file.metadata.data, file.name, errno); return false; @@ -205,63 +276,76 @@ private: return false; } - IncFsSize size = file.size; - IncFsSize remaining = size; - IncFsSize totalSize = 0; - IncFsBlockIndex blockIdx = 0; - while (remaining > 0) { - constexpr auto capacity = BUFFER_SIZE; - auto size = buffer.size(); - if (capacity - size < INCFS_DATA_FILE_BLOCK_SIZE) { - if (!flashToIncFs(incfsFd, false, &blocks, &blockIdx, &buffer)) { - return false; - } - continue; + for (auto&& input : inputs) { + if (!copyToIncFs(incfsFd, input.size, input.kind, input.fd, input.waitOnEof, + &buffer, &blocks)) { + ALOGE("Failed to copy data to IncFS file for metadata: %.*s, final file name " + "is: %s. " + "Error %d", + int(file.metadata.size), file.metadata.data, file.name, errno); + return false; } + } + } - auto toRead = std::min<IncFsSize>(remaining, capacity - size); - buffer.resize(size + toRead); - auto read = ::read(incomingFd, buffer.data() + size, toRead); - if (read == 0) { - if (stdin) { - // eof of stdin, waiting... - ALOGE("eof of stdin, waiting...: %d, remaining: %d, block: %d, read: %d", - int(totalSize), int(remaining), int(blockIdx), int(read)); - using namespace std::chrono_literals; - std::this_thread::sleep_for(10ms); - continue; - } - break; - } - if (read < 0) { - ALOGE("Underlying file read error: %.*s: %d", int(file.metadata.size), - file.metadata.data, int(read)); + ALOGE("All done."); + return true; + } + + bool copyToIncFs(borrowed_fd incfsFd, IncFsSize size, IncFsBlockKind kind, + borrowed_fd incomingFd, bool waitOnEof, std::vector<char>* buffer, + std::vector<IncFsDataBlock>* blocks) { + IncFsSize remaining = size; + IncFsSize totalSize = 0; + IncFsBlockIndex blockIdx = 0; + while (remaining > 0) { + constexpr auto capacity = BUFFER_SIZE; + auto size = buffer->size(); + if (capacity - size < INCFS_DATA_FILE_BLOCK_SIZE) { + if (!flashToIncFs(incfsFd, kind, false, &blockIdx, buffer, blocks)) { return false; } + continue; + } - buffer.resize(size + read); - remaining -= read; - totalSize += read; + auto toRead = std::min<IncFsSize>(remaining, capacity - size); + buffer->resize(size + toRead); + auto read = ::read(incomingFd.get(), buffer->data() + size, toRead); + if (read == 0) { + if (waitOnEof) { + // eof of stdin, waiting... + ALOGE("eof of stdin, waiting...: %d, remaining: %d, block: %d, read: %d", + int(totalSize), int(remaining), int(blockIdx), int(read)); + using namespace std::chrono_literals; + std::this_thread::sleep_for(10ms); + continue; + } + break; } - if (!buffer.empty() && !flashToIncFs(incfsFd, true, &blocks, &blockIdx, &buffer)) { + if (read < 0) { return false; } - } - ALOGE("All done."); + buffer->resize(size + read); + remaining -= read; + totalSize += read; + } + if (!buffer->empty() && !flashToIncFs(incfsFd, kind, true, &blockIdx, buffer, blocks)) { + return false; + } return true; } - bool flashToIncFs(int incfsFd, bool eof, std::vector<IncFsDataBlock>* blocks, - IncFsBlockIndex* blockIdx, std::vector<char>* buffer) { + bool flashToIncFs(borrowed_fd incfsFd, IncFsBlockKind kind, bool eof, IncFsBlockIndex* blockIdx, + std::vector<char>* buffer, std::vector<IncFsDataBlock>* blocks) { int consumed = 0; const auto fullBlocks = buffer->size() / INCFS_DATA_FILE_BLOCK_SIZE; for (int i = 0; i < fullBlocks; ++i) { const auto inst = IncFsDataBlock{ - .fileFd = incfsFd, + .fileFd = incfsFd.get(), .pageIndex = (*blockIdx)++, .compression = INCFS_COMPRESSION_KIND_NONE, - .kind = INCFS_BLOCK_KIND_DATA, + .kind = kind, .dataSize = INCFS_DATA_FILE_BLOCK_SIZE, .data = buffer->data() + consumed, }; @@ -271,10 +355,10 @@ private: const auto remain = buffer->size() - fullBlocks * INCFS_DATA_FILE_BLOCK_SIZE; if (remain && eof) { const auto inst = IncFsDataBlock{ - .fileFd = incfsFd, + .fileFd = incfsFd.get(), .pageIndex = (*blockIdx)++, .compression = INCFS_COMPRESSION_KIND_NONE, - .kind = INCFS_BLOCK_KIND_DATA, + .kind = kind, .dataSize = static_cast<uint16_t>(remain), .data = buffer->data() + consumed, }; |