Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 74b4af53 authored by Billy Lau's avatar Billy Lau
Browse files

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
parent 99deef06
Loading
Loading
Loading
Loading
+58 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+4 −18
Original line number Diff line number Diff line
@@ -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;
        }
    }
}