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

Commit b7c2ccd8 authored by Shervin Oloumi's avatar Shervin Oloumi Committed by Shervin Oloumi
Browse files

adds logging code for InitAppScanReported

Introduces the InitAppScanMetrics class for handling the stats related
to boot-time app scan performed by package manager. The class is focused
on the verification phase of the scan and logs stats such as the
duration of verification, its outcome and various APK attributes.
Stats are logged using the atom InitAppScanReported.

Adds the logging code to InstallPackageHelper to initialize the metric
and record it on per-APK basis. The metric is only recorded for updated
system apps. This is because the main goal is to evaluate the signature
verification phase, specifically for apps that are allow-listed for Full
Stack Integrity (FSI) check. System directory apps are covered by
Verified Boot (VB) not FSI, and FSI checks do not apply to non-system
apps in the data directory.

Bug: 422817705
Flag: EXEMPT Metrics
Test: statsd_testdrive -e 1171
Cq-Depend: CL:34095157
Change-Id: I5de84f5aa94b21974c9c5d8f0732155a56ef4e8b
parent 41145db2
Loading
Loading
Loading
Loading
+173 −0
Original line number Diff line number Diff line
/*
    * Copyright (C) 2025 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 com.android.server.pm;

import android.content.pm.PackageManager;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.os.SystemClock;

import com.android.internal.util.FrameworkStatsLog;

/**
 * A helper class to collect and log metrics for the initial scan of a single package during system
 * boot. This class uses a builder pattern to gather metric data before logging.
 */
public final class InitAppScanMetrics {

    private boolean mIsFsiEnabled;
    private int mNumApkSplits;
    private int mSignatureSchemeVersion =
            FrameworkStatsLog.INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__UNKNOWN;

    private final long mTotalScanStartTimeMillis;
    private long mTotalScanDurationMillis;

    private int mInitAppScanOutcome =
            FrameworkStatsLog.INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__UNSPECIFIED;

    /** Starts the timer for the total scan duration. */
    public InitAppScanMetrics() {
        this.mTotalScanStartTimeMillis = SystemClock.uptimeMillis();
    }

    /**
     * Translates a package managers signature scheme version into the corresponding init app scan
     * metric signature scheme version.
     *
     * @param signatureSchemeVersion A package manager SigningDetails.SignatureScheme.* enum value.
     * @return The corresponding init app scan metric signature scheme enum value.
     */
    private static int translateToSignatureSchemeVersion(int signatureSchemeVersion) {
        switch (signatureSchemeVersion) {
            case SignatureSchemeVersion.UNKNOWN:
                return FrameworkStatsLog.INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__UNKNOWN;
            case SignatureSchemeVersion.JAR:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__JAR;
            case SignatureSchemeVersion.SIGNING_BLOCK_V2:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__V2;
            case SignatureSchemeVersion.SIGNING_BLOCK_V3:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__V3;
            case SignatureSchemeVersion.SIGNING_BLOCK_V4:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__V4;

            default:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__SIGNATURE_SCHEME_VERSION__UNKNOWN;
        }
    }

    /**
     * Translates a package manager installation error code into the corresponding init app scan
     * outcome for metrics logging.
     *
     * @param returnCode A PackageManager.INSTALL_* error code.
     * @return The corresponding app scan outcome enum value.
     */
    private static int translateToInitAppScanOutcome(int returnCode) {
        switch (returnCode) {
            case PackageManager.INSTALL_SUCCEEDED:
                return FrameworkStatsLog.INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__SUCCESS;
            case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_NO_CERTIFICATES;
            case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_VERIFICATION;
            case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_UPDATE_INCOMPATIBLE;
            case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_INCONSISTENT_CERTIFICATES;
            case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_CERTIFICATE_ENCODING;
            case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE:
            case PackageManager.INSTALL_FAILED_INVALID_APK:
            case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED:
            case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_SCAN_VALIDATION;

            default:
                return FrameworkStatsLog
                        .INIT_APP_SCAN_REPORTED__INIT_APP_SCAN_OUTCOME__FAILURE_OTHER;
        }
    }

    /**
     * Sets whether the scanned package is allow-listed for FSI check.
     *
     * @param isFsiEnabled True if the package has FSI enabled, false otherwise.
     * @return This {@link InitAppScanMetrics} instance for chaining.
     */
    public InitAppScanMetrics setIsFsiEnabled(boolean isFsiEnabled) {
        this.mIsFsiEnabled = isFsiEnabled;
        return this;
    }

    /**
     * Sets the number of APK splits for the scanned package.
     *
     * @param numApkSplits number of APK splits.
     * @return This {@link InitAppScanMetrics} instance for chaining.
     */
    public InitAppScanMetrics setNumApkSplits(int numApkSplits) {
        this.mNumApkSplits = numApkSplits;
        return this;
    }

    /**
     * Sets the signature scheme version used for package verification.
     *
     * @param signatureSchemeVersion The version of the signature scheme.
     * @return This {@link InitAppScanMetrics} instance for chaining.
     */
    public InitAppScanMetrics setSignatureSchemeVersion(int signatureSchemeVersion) {
        this.mSignatureSchemeVersion =
            translateToSignatureSchemeVersion(signatureSchemeVersion);
        return this;
    }

    /**
     * Sets the final outcome of the APK scan.
     *
     * @param returnCode A PackageManager.INSTALL_* error code.
     * @return This {@link InitAppScanMetrics} instance for chaining.
     */
    public InitAppScanMetrics setInitAppScanOutcome(int returnCode) {
        this.mInitAppScanOutcome = translateToInitAppScanOutcome(returnCode);
        return this;
    }

    /** Finalizes and logs the collected metrics to FrameworkStatsLog. */
    public void log() {
        this.mTotalScanDurationMillis = SystemClock.uptimeMillis() - mTotalScanStartTimeMillis;

        FrameworkStatsLog.write(
                FrameworkStatsLog.INIT_APP_SCAN_REPORTED,
                mIsFsiEnabled,
                mNumApkSplits,
                mSignatureSchemeVersion,
                mTotalScanDurationMillis,
                mInitAppScanOutcome);
    }
}
+252 −226
Original line number Diff line number Diff line
@@ -4337,10 +4337,13 @@ final class InstallPackageHelper {
            @ParsingPackageUtils.ParseFlags int parseFlags,
            @PackageManagerService.ScanFlags int scanFlags,
            @Nullable UserHandle user) throws PackageManagerException {
        final InitAppScanMetrics metrics = new InitAppScanMetrics();
        boolean shouldLogInitAppScanMetric = false;
        try {
            final boolean scanSystemPartition =
                (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
        final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
                scanFlags, user, null);
            final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage,
                    parseFlags, scanFlags, user, null);
            final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
            final PackageSetting originalPkgSetting = initialScanRequest.mOriginalPkgSetting;
            final PackageSetting pkgSetting =
@@ -4391,6 +4394,7 @@ final class InstallPackageHelper {
                }
            } // End of mLock

            shouldLogInitAppScanMetric = !scanSystemPartition && isSystemPkgUpdated;
            final boolean newPkgChangedPaths = pkgAlreadyExists
                    && !pkgSetting.getPathString().equals(parsedPackage.getPath());
            final boolean newPkgVersionGreater = pkgAlreadyExists
@@ -4436,8 +4440,8 @@ final class InstallPackageHelper {
                // For some updated system packages, during addForInit we want to ensure the
                // PackageSetting has the correct SigningDetails compares to the original version on
                // the system partition. For the check to happen later during the /data scan, update
            // the disabled package setting per the original APK on a system partition so that it
            // can be trusted during reconcile.
                // the disabled package setting per the original APK on a system partition so that
                // it can be trusted during reconcile.
                if (needSignatureMatchToSystem(parsedPackage.getPackageName())) {
                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
                    final ParseResult<SigningDetails> result =
@@ -4450,22 +4454,23 @@ final class InstallPackageHelper {
                    disabledPkgSetting.setSigningDetails(result.getResult());
                }

            // In the case of a skipped package, commitReconciledScanResultLocked is not called to
            // add the object to the "live" data structures, so this is the final mutation step
            // for the package. Which means it needs to be finalized here to cache derived fields.
            // This is relevant for cases where the disabled system package is used for flags or
            // other metadata.
                // In the case of a skipped package, commitReconciledScanResultLocked is not called
                // to add the object to the "live" data structures, so this is the final mutation
                // step for the package. Which means it needs to be finalized here to cache derived
                // fields. This is relevant for cases where the disabled system package is used for
                // flags or other metadata.
                parsedPackage.hideAsFinal();
                throw PackageManagerException.ofInternalError(
                        "Package " + parsedPackage.getPackageName()
                                + " at " + parsedPackage.getPath() + " ignored: updated version "
                    + (pkgAlreadyExists ? String.valueOf(pkgSetting.getVersionCode()) : "unknown")
                                + (pkgAlreadyExists
                                        ? String.valueOf(pkgSetting.getVersionCode()) : "unknown")
                                + " better than this " + parsedPackage.getLongVersionCode(),
                        PackageManagerException.INTERNAL_ERROR_UPDATED_VERSION_BETTER_THAN_SYSTEM);
            }

        // Verify certificates against what was last scanned. Force re-collecting certificate in two
        // special cases:
            // Verify certificates against what was last scanned. Force re-collecting certificate in
            // two special cases:
            // 1) when scanning system, force re-collect only if system is upgrading.
            // 2) when scanning /data, force re-collect only if the package name is allowlisted.
            final boolean forceCollect = scanSystemPartition ? isUpgrade
@@ -4474,13 +4479,21 @@ final class InstallPackageHelper {
                Slog.d(TAG, "Force collect certificate of " + parsedPackage.getPackageName());
            }

        // APK verification can be skipped during certificate collection, only if the file is in a
        // verified partition.
            // APK verification can be skipped during certificate collection, only if the file is in
            // a verified partition.
            final boolean skipVerify = scanSystemPartition;
            ScanPackageUtils.collectCertificatesLI(pkgSetting, parsedPackage,
                    mPm.getSettingsVersionForPackage(parsedPackage), forceCollect, skipVerify,
                    mPm.isPreNMR1Upgrade());

            // Populate the InitAppScanMetrics object since all the variables are defined now.
            metrics.setIsFsiEnabled(forceCollect)
                    .setNumApkSplits(parsedPackage.getSplitCodePaths() == null
                            ? 0
                            : parsedPackage.getSplitCodePaths().length)
                    .setSignatureSchemeVersion(
                            parsedPackage.getSigningDetails().getSignatureSchemeVersion());

            // Reset profile if the application version is changed
            maybeClearProfilesForUpgradesLI(pkgSetting, parsedPackage);

@@ -4571,7 +4584,20 @@ final class InstallPackageHelper {
            final long firstInstallTime = System.currentTimeMillis();
            final ScanResult scanResult = scanPackageNew(parsedPackage, parseFlags,
                    scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
            // Set scan outcome as successful for InitAppScanMetrics.
            metrics.setInitAppScanOutcome(PackageManager.INSTALL_SUCCEEDED);
            return new Pair<>(scanResult, shouldHideSystemApp);
        } catch (PackageManagerException e) {
            // Set scan outcome failure type for InitAppScanMetrics.
            metrics.setInitAppScanOutcome(e.error);
            throw e;
        } finally {
            // Finalizes the total scan duration and logs the InitAppScanMetrics metric. The metric
            // is only logged for updated system apps.
            if (shouldLogInitAppScanMetric) {
                metrics.log();
            }
        }
    }

    private static boolean hasLauncherEntry(ParsedPackage parsedPackage) {