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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user