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

Commit 40d352d4 authored by Victor Hsieh's avatar Victor Hsieh Committed by Android (Google) Code Review
Browse files

Merge "Support measuring split"

parents ba2afc5b 38b94091
Loading
Loading
Loading
Loading
+17 −18
Original line number Diff line number Diff line
@@ -66,24 +66,6 @@ public class BinaryTransparencyManager {
        }
    }

    /**
     * Gets binary measurements of all installed APEXs, each packed in a Bundle.
     * @return A List of {@link android.os.Bundle}s with the following keys:
     *         {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO}
     *         {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM}
     *         {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST}
     */
    // TODO(b/259422958): Fix static constants referenced here - should be defined here
    @NonNull
    public List getApexInfo() {
        try {
            Slog.d(TAG, "Calling backend's getApexInfo()");
            return mService.getApexInfo();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Collects the APEX information on the device.
     *
@@ -116,4 +98,21 @@ public class BinaryTransparencyManager {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Collects the silent installed MBA information on the device.
     *
     * @return A List containing the MBA info of silent installed.
     * @hide
     */
    @NonNull
    public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo(
            Bundle packagesToSkip) {
        try {
            Slog.d(TAG, "Calling backend's collectAllSilentInstalledMbaInfo()");
            return mService.collectAllSilentInstalledMbaInfo(packagesToSkip);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@ import android.os.Bundle;
interface IBinaryTransparencyService {
    String getSignedImageInfo();

    List getApexInfo();

    void recordMeasurementsForAllPackages();

    parcelable ApexInfo {
@@ -60,4 +58,5 @@ interface IBinaryTransparencyService {
    /** Test only */
    List<ApexInfo> collectAllApexInfo(boolean includeTestOnly);
    List<AppInfo> collectAllUpdatedPreloadInfo(in Bundle packagesToSkip);
    List<AppInfo> collectAllSilentInstalledMbaInfo(in Bundle packagesToSkip);
}
 No newline at end of file
+96 −123
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApexStagedEvent;
import android.content.pm.ApplicationInfo;
import android.content.pm.Checksum;
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.IPackageManagerNative;
import android.content.pm.IStagedApexObserver;
@@ -84,6 +85,7 @@ import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.pm.ApexManager;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.AndroidPackageSplit;
import com.android.server.pm.pkg.PackageState;

import libcore.util.HexEncoding;
@@ -120,15 +122,6 @@ public class BinaryTransparencyService extends SystemService {

    static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;

    @VisibleForTesting
    static final String BUNDLE_PACKAGE_NAME = "package-name";
    @VisibleForTesting
    static final String BUNDLE_PACKAGE_IS_APEX = "package-is-apex";
    @VisibleForTesting
    static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo";
    @VisibleForTesting
    static final String BUNDLE_CONTENT_DIGEST = "content-digest";

    static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";

    // used for indicating any type of error during MBA measurement
@@ -170,29 +163,6 @@ public class BinaryTransparencyService extends SystemService {
            return mVbmetaDigest;
        }

        @Override
        public List getApexInfo() {
            List<Bundle> results = new ArrayList<>();

            for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
                PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
                        packageInfo.packageName);
                if (packageState == null) {
                    Slog.w(TAG, "Package state is unavailable, ignoring the package "
                            + packageInfo.packageName);
                    continue;
                }
                Bundle apexMeasurement = measurePackage(packageState);
                if (apexMeasurement == null) {
                    Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath());
                    continue;
                }
                results.add(apexMeasurement);
            }

            return results;
        }

        /**
         * A helper function to compute the SHA256 digest of APK package signer.
         * @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}.
@@ -217,58 +187,102 @@ public class BinaryTransparencyService extends SystemService {
            return resultList.toArray(new String[1]);
        }

        /**
         * Perform basic measurement (i.e. content digest) on a given package.
        /*
         * Perform basic measurement (i.e. content digest) on a given app, including the split APKs.
         *
         * @param packageState The package to be measured.
         * @return a {@link android.os.Bundle} that packs the measurement result with the following
         *         keys: {@link #BUNDLE_PACKAGE_NAME},
         *               {@link #BUNDLE_PACKAGE_IS_APEX}
         *               {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM}
         *               {@link #BUNDLE_CONTENT_DIGEST}
         * @param mbaStatus Assign this value of MBA status to the returned elements.
         * @return a @{@code List<IBinaryTransparencyService.AppInfo>}
         */
        private @Nullable Bundle measurePackage(PackageState packageState) {
            Bundle result = new Bundle();

        private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
                PackageState packageState, int mbaStatus) {
            // compute content digest
            if (DEBUG) {
                Slog.d(TAG, "Computing content digest for " + packageState.getPackageName() + " at "
                        + packageState.getPath());
            }

            var results = new ArrayList<IBinaryTransparencyService.AppInfo>();

            // Same attributes across base and splits.
            String packageName = packageState.getPackageName();
            long versionCode = packageState.getVersionCode();
            String[] signerDigests =
                    computePackageSignerSha256Digests(packageState.getSigningInfo());

            AndroidPackage pkg = packageState.getAndroidPackage();
            if (pkg == null) {
                Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
                return null;
            for (AndroidPackageSplit split : pkg.getSplits()) {
                var appInfo = new IBinaryTransparencyService.AppInfo();
                appInfo.packageName = packageName;
                appInfo.longVersion = versionCode;
                appInfo.splitName = split.getName();  // base's split name is null
                // Signer digests are consistent between splits, guaranteed by Package Manager.
                appInfo.signerDigests = signerDigests;
                appInfo.mbaStatus = mbaStatus;

                // Only digest and split name are different between splits.
                Checksum checksum = measureApk(split.getPath());
                appInfo.digest = checksum.getValue();
                appInfo.digestAlgorithm = checksum.getType();

                results.add(appInfo);
            }

            // InstallSourceInfo is only available per package name, so store it only on the base
            // APK. It's not current currently available in PackageState (there's a TODO), to we
            // need to extract manually with another call.
            //
            // Base APK is already the 0-th split from getSplits() and can't be null.
            AppInfo base = results.get(0);
            InstallSourceInfo installSourceInfo = getInstallSourceInfo(
                    packageState.getPackageName());
            if (installSourceInfo != null) {
                base.initiator = installSourceInfo.getInitiatingPackageName();
                SigningInfo initiatorSignerInfo =
                        installSourceInfo.getInitiatingPackageSigningInfo();
                if (initiatorSignerInfo != null) {
                    base.initiatorSignerDigests =
                        computePackageSignerSha256Digests(initiatorSignerInfo);
                }
                base.installer = installSourceInfo.getInstallingPackageName();
                base.originator = installSourceInfo.getOriginatingPackageName();
            }

            return results;
        }
            Map<Integer, byte[]> contentDigests = computeApkContentDigest(pkg.getBaseApkPath());
            result.putString(BUNDLE_PACKAGE_NAME, pkg.getPackageName());

        /**
         * Perform basic measurement (i.e. content digest) on a given APK.
         *
         * @param apkPath The APK (or APEX, since it's also an APK) file to be measured.
         * @return a {@link android.content.pm.Checksum} with preferred digest algorithm type and
         *         the checksum.
         */
        private @Nullable Checksum measureApk(@NonNull String apkPath) {
            // compute content digest
            Map<Integer, byte[]> contentDigests = computeApkContentDigest(apkPath);
            if (contentDigests == null) {
                Slog.d(TAG, "Failed to compute content digest for " + pkg.getBaseApkPath());
                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
                result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
                return result;
                Slog.d(TAG, "Failed to compute content digest for " + apkPath);
                return new Checksum(0, new byte[] { -1 });
            }

            // in this iteration, we'll be supporting only 2 types of digests:
            // CHUNKED_SHA256 and CHUNKED_SHA512.
            // And only one of them will be available per package.
            if (contentDigests.containsKey(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)) {
                Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
                result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
                return new Checksum(
                        Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
                        contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256));
            } else if (contentDigests.containsKey(
                    ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) {
                Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
                result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
                return new Checksum(
                        Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
                        contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512));
            } else {
                // TODO(b/259423111): considering putting the raw values for the algorithm & digest
                //  into the bundle to track potential other digest algorithms that may be in use
                result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
                result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
                return new Checksum(0, new byte[] { -1 });
            }
            result.putBoolean(BUNDLE_PACKAGE_IS_APEX, packageState.isApex());

            return result;
        }


@@ -330,7 +344,7 @@ public class BinaryTransparencyService extends SystemService {
            if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
                // lastly measure all newly installed MBAs
                List<IBinaryTransparencyService.AppInfo> allMbaInfo =
                        collectAllMbaInfo(packagesMeasured);
                        collectAllSilentInstalledMbaInfo(packagesMeasured);
                for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
                    packagesMeasured.putBoolean(appInfo.packageName, true);
                    writeAppInfoToLog(appInfo);
@@ -356,18 +370,22 @@ public class BinaryTransparencyService extends SystemService {
                    continue;
                }

                Bundle apexMeasurement = measurePackage(packageState);
                if (apexMeasurement == null) {
                    Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath());
                AndroidPackage pkg = packageState.getAndroidPackage();
                if (pkg == null) {
                    Slog.w(TAG, "Skipping the missing APK in " + pkg.getPath());
                    continue;
                }
                Checksum apexChecksum = measureApk(pkg.getPath());
                if (apexChecksum == null) {
                    Slog.w(TAG, "Skipping the missing APEX in " + pkg.getPath());
                    continue;
                }

                var apexInfo = new IBinaryTransparencyService.ApexInfo();
                apexInfo.packageName = packageState.getPackageName();
                apexInfo.longVersion = packageState.getVersionCode();
                apexInfo.digest = apexMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
                apexInfo.digestAlgorithm =
                        apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
                apexInfo.digest = apexChecksum.getValue();
                apexInfo.digestAlgorithm = apexChecksum.getType();
                apexInfo.signerDigests =
                        computePackageSignerSha256Digests(packageState.getSigningInfo());

@@ -398,28 +416,16 @@ public class BinaryTransparencyService extends SystemService {
                Slog.d(TAG, "Preload " + packageState.getPackageName() + " at "
                        + packageState.getPath() + " has likely been updated.");

                Bundle packageMeasurement = measurePackage(packageState);
                if (packageMeasurement == null) {
                    Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
                    return;
                }

                var appInfo = new IBinaryTransparencyService.AppInfo();
                appInfo.packageName = packageState.getPackageName();
                appInfo.longVersion = packageState.getVersionCode();
                appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
                appInfo.digestAlgorithm =
                        packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
                appInfo.signerDigests =
                        computePackageSignerSha256Digests(packageState.getSigningInfo());
                appInfo.mbaStatus = MBA_STATUS_UPDATED_PRELOAD;

                results.add(appInfo);
                List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
                        packageState, MBA_STATUS_UPDATED_PRELOAD);
                results.addAll(resultsForApp);
            });
            return results;
        }

        public List<IBinaryTransparencyService.AppInfo> collectAllMbaInfo(Bundle packagesToSkip) {
        @Override
        public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo(
                Bundle packagesToSkip) {
            var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
            for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
                if (packagesToSkip.containsKey(packageInfo.packageName)) {
@@ -433,42 +439,9 @@ public class BinaryTransparencyService extends SystemService {
                    continue;
                }

                Bundle packageMeasurement = measurePackage(packageState);
                if (packageMeasurement == null) {
                    Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
                    continue;
                }
                if (DEBUG) {
                    Slog.d(TAG,
                            "Extracting InstallSourceInfo for " + packageState.getPackageName());
                }
                var appInfo = new IBinaryTransparencyService.AppInfo();
                appInfo.packageName = packageState.getPackageName();
                appInfo.longVersion = packageState.getVersionCode();
                appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
                appInfo.digestAlgorithm =
                    packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
                appInfo.signerDigests =
                        computePackageSignerSha256Digests(packageState.getSigningInfo());
                appInfo.mbaStatus = MBA_STATUS_NEW_INSTALL;

                // Install source isn't currently available in PackageState (there's a TODO).
                // Extract manually with another call.
                InstallSourceInfo installSourceInfo = getInstallSourceInfo(
                        packageState.getPackageName());
                if (installSourceInfo != null) {
                    appInfo.initiator = installSourceInfo.getInitiatingPackageName();
                    SigningInfo initiatorSignerInfo =
                            installSourceInfo.getInitiatingPackageSigningInfo();
                    if (initiatorSignerInfo != null) {
                        appInfo.initiatorSignerDigests =
                                computePackageSignerSha256Digests(initiatorSignerInfo);
                    }
                    appInfo.installer = installSourceInfo.getInstallingPackageName();
                    appInfo.originator = installSourceInfo.getOriginatingPackageName();
                }

                results.add(appInfo);
                List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
                        packageState, MBA_STATUS_NEW_INSTALL);
                results.addAll(resultsForApp);
            }
            return results;
        }
+0 −31
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -165,36 +164,6 @@ public class BinaryTransparencyServiceTest {
                        BinaryTransparencyService.VBMETA_DIGEST_UNAVAILABLE), result);
    }

    @Test
    public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException {
        prepApexInfo();
        List result = mTestInterface.getApexInfo();
        Assert.assertNotNull("Apex info map should not be null", result);
        // TODO(265244016): When PackageManagerInternal is a mock, it's harder to keep the
        // `measurePackage` working in unit test. Disable it for now. We may need more refactoring
        // or cover this in integration tests.
        // Assert.assertFalse("Apex info map should not be empty", result.isEmpty());
    }

    @Test
    public void getApexInfo_postInitialize_returnsActualApexs()
            throws RemoteException, PackageManager.NameNotFoundException {
        prepApexInfo();
        List resultList = mTestInterface.getApexInfo();

        PackageManager pm = mContext.getPackageManager();
        Assert.assertNotNull(pm);
        List<Bundle> castedResult = (List<Bundle>) resultList;
        for (Bundle resultBundle : castedResult) {
            String packageName = resultBundle.getString(
                    BinaryTransparencyService.BUNDLE_PACKAGE_NAME);
            Assert.assertNotNull("Package name for APEX should not be null", packageName);
            Assert.assertTrue(packageName + "is not an APEX!",
                    resultBundle.getBoolean(
                            BinaryTransparencyService.BUNDLE_PACKAGE_IS_APEX));
        }
    }

    @Test
    public void testCollectBiometricProperties_disablesFeature() {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS,
+140 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.transparency.test;

import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;

import junit.framework.TestCase;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
 *
 * <code> private class InstallMultiple extends BaseInstallMultiple&lt;InstallMultiple&gt; { public
 * InstallMultiple() { super(getDevice(), null); } } </code>
 */
/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {

    private final ITestDevice mDevice;
    private final IBuildInfo mBuild;

    private final List<String> mArgs = new ArrayList<>();
    private final Map<File, String> mFileToRemoteMap = new HashMap<>();

    /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) {
        mDevice = device;
        mBuild = buildInfo;
        addArg("-g");
    }

    T addArg(String arg) {
        mArgs.add(arg);
        return (T) this;
    }

    T addFile(String filename) throws FileNotFoundException {
        return addFile(filename, filename);
    }

    T addFile(String filename, String remoteName) throws FileNotFoundException {
        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
        mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName);
        return (T) this;
    }

    T inheritFrom(String packageName) {
        addArg("-r");
        addArg("-p " + packageName);
        return (T) this;
    }

    void run() throws DeviceNotAvailableException {
        run(true);
    }

    void runExpectingFailure() throws DeviceNotAvailableException {
        run(false);
    }

    private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
        final ITestDevice device = mDevice;

        // Create an install session
        final StringBuilder cmd = new StringBuilder();
        cmd.append("pm install-create");
        for (String arg : mArgs) {
            cmd.append(' ').append(arg);
        }

        String result = device.executeShellCommand(cmd.toString());
        TestCase.assertTrue(result, result.startsWith("Success"));

        final int start = result.lastIndexOf("[");
        final int end = result.lastIndexOf("]");
        int sessionId = -1;
        try {
            if (start != -1 && end != -1 && start < end) {
                sessionId = Integer.parseInt(result.substring(start + 1, end));
            }
        } catch (NumberFormatException e) {
            throw new IllegalStateException("Failed to parse install session: " + result);
        }
        if (sessionId == -1) {
            throw new IllegalStateException("Failed to create install session: " + result);
        }

        // Push our files into session. Ideally we'd use stdin streaming,
        // but ddmlib doesn't support it yet.
        for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) {
            final File file = entry.getKey();
            final String remoteName  = entry.getValue();
            final String remotePath = "/data/local/tmp/" + file.getName();
            if (!device.pushFile(file, remotePath)) {
                throw new IllegalStateException("Failed to push " + file);
            }

            cmd.setLength(0);
            cmd.append("pm install-write");
            cmd.append(' ').append(sessionId);
            cmd.append(' ').append(remoteName);
            cmd.append(' ').append(remotePath);

            result = device.executeShellCommand(cmd.toString());
            TestCase.assertTrue(result, result.startsWith("Success"));
        }

        // Everything staged; let's pull trigger
        cmd.setLength(0);
        cmd.append("pm install-commit");
        cmd.append(' ').append(sessionId);

        result = device.executeShellCommand(cmd.toString());
        if (expectingSuccess) {
            TestCase.assertTrue(result, result.contains("Success"));
        } else {
            TestCase.assertFalse(result, result.contains("Success"));
        }
    }
}
Loading