From 74b4af53ca57a1dc5a94b1da895c8ab681ec6ecd Mon Sep 17 00:00:00 2001 From: Billy Lau Date: Tue, 15 Feb 2022 19:32:27 -0800 Subject: [PATCH] Fix potential OOM issues when APEXs are too large. On low RAM devices, there can be potentially OOM issues when APEXs are too large, causing large buffers to be allocated when computing the SHA256 digest of those APEX packages. This change introduces the usage of DigestInputStream with different buffer sizes according to the state of device (whether it is a low ram device or not) to cap the memory usage. Buffer size is currently derived experimentally at either 1kB or 1MB. Bug: 217596264 Test: Manual. Change-Id: I1964ef9d7047496a758c7f427910f116be89fc51 --- core/java/android/util/PackageUtils.java | 58 +++++++++++++++++++ .../server/BinaryTransparencyService.java | 22 ++----- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java index ff04825f788f4..c5ab82dbf0c51 100644 --- a/core/java/android/util/PackageUtils.java +++ b/core/java/android/util/PackageUtils.java @@ -18,13 +18,17 @@ package android.util; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.content.pm.Signature; import android.text.TextUtils; import libcore.util.HexEncoding; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -35,6 +39,9 @@ import java.util.Arrays; */ public final class PackageUtils { + private static final int LOW_RAM_BUFFER_SIZE_BYTES = 1 * 1000; // 1 kB + private static final int HIGH_RAM_BUFFER_SIZE_BYTES = 1 * 1000 * 1000; // 1 MB + private PackageUtils() { /* hide constructor */ } @@ -162,4 +169,55 @@ public final class PackageUtils { return TextUtils.join(separator, pieces); } + + /** + * @see #computeSha256DigestForLargeFile(String, String) + */ + public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath) { + return computeSha256DigestForLargeFile(filePath, null); + } + + /** + * Computes the SHA256 digest of large files. + * @param filePath The path to which the file's content is to be hashed. + * @param separator Separator between each pair of characters, such as colon, or null to omit. + * @return The digest or null if an error occurs. + */ + public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, + @Nullable String separator) { + MessageDigest messageDigest; + try { + messageDigest = MessageDigest.getInstance("SHA256"); + messageDigest.reset(); + } catch (NoSuchAlgorithmException e) { + // this shouldn't happen! + return null; + } + + boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); + int bufferSize = isLowRamDevice ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES; + + File f = new File(filePath); + try { + DigestInputStream digestStream = new DigestInputStream(new FileInputStream(f), + messageDigest); + byte[] buffer = new byte[bufferSize]; + while (digestStream.read(buffer) != -1); + } catch (IOException e) { + return null; + } + + byte[] resultBytes = messageDigest.digest(); + + if (separator == null) { + return HexEncoding.encodeToString(resultBytes, true); + } + + int length = resultBytes.length; + String[] pieces = new String[length]; + for (int index = 0; index < length; index++) { + pieces[index] = HexEncoding.encodeToString(resultBytes[index], true); + } + return TextUtils.join(separator, pieces); + } } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index d218af39405dc..6986d3bbe5852 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -36,11 +36,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IBinaryTransparencyService; import com.android.internal.util.FrameworkStatsLog; -import java.io.File; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; -import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -434,7 +431,7 @@ public class BinaryTransparencyService extends SystemService { entry.setValue(packageInfo.lastUpdateTime); // compute the digest for the updated package - String sha256digest = computeSha256DigestOfFile( + String sha256digest = PackageUtils.computeSha256DigestForLargeFile( packageInfo.applicationInfo.sourceDir); if (sha256digest == null) { Slog.e(TAG, "Failed to compute SHA256sum for file at " @@ -471,7 +468,7 @@ public class BinaryTransparencyService extends SystemService { ApplicationInfo appInfo = packageInfo.applicationInfo; // compute SHA256 for these APEXs - String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir); + String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir); if (sha256digest == null) { Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", packageInfo.packageName)); @@ -506,7 +503,8 @@ public class BinaryTransparencyService extends SystemService { ApplicationInfo appInfo = packageInfo.applicationInfo; // compute SHA256 digest for these modules - String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir); + String sha256digest = PackageUtils.computeSha256DigestForLargeFile( + appInfo.sourceDir); if (sha256digest == null) { Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", packageName)); @@ -525,16 +523,4 @@ public class BinaryTransparencyService extends SystemService { } } - @Nullable - private String computeSha256DigestOfFile(@NonNull String pathToFile) { - File apexFile = new File(pathToFile); - - try { - byte[] apexFileBytes = Files.readAllBytes(apexFile.toPath()); - return PackageUtils.computeSha256Digest(apexFileBytes); - } catch (IOException e) { - Slog.e(TAG, String.format("I/O error occurs when reading from %s", pathToFile)); - return null; - } - } }