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

Commit a4c17763 authored by Song Chun Fan's avatar Song Chun Fan
Browse files

[2/N] implementation of verifier controller and status tracker

+ VerifierController controls the binding of the verification service agent
+ VerificationStatusTracker manages the timeout status of a verification
+ If a verifier is connected, wait for the verifier response before starting the rest of the verification
+ Unit testing

Test: atest com.android.server.pm.verify.pkg.VerifierControllerTest
Test: atest com.android.server.pm.verify.pkg.VerificationStatusTrackerTest
Test: atest CtsPackageManagerTestCases:VerifierServiceTest

FLAG: android.content.pm.verification_service

BUG: 360129103
BUG: 360129657

Change-Id: I487c56a9e1d81c0367aa8309b792b7c61dfe9fb4
parent c95e3bb1
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -126,6 +126,7 @@ import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.utils.RequestThrottle;
import com.android.server.pm.verify.pkg.VerifierController;

import libcore.io.IoUtils;

@@ -213,6 +214,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
    private final StagingManager mStagingManager;

    private AppOpsManager mAppOps;
    private final VerifierController mVerifierController;

    private final HandlerThread mInstallThread;
    private final Handler mInstallHandler;
@@ -325,6 +327,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        mGentleUpdateHelper = new GentleUpdateHelper(
                context, mInstallThread.getLooper(), new AppStateHelper(context));
        mPackageArchiver = new PackageArchiver(mContext, mPm);
        mVerifierController = new VerifierController(mContext, mInstallHandler);

        LocalServices.getService(SystemServiceManager.class).startService(
                new Lifecycle(context, this));
@@ -521,7 +524,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                        try {
                            session = PackageInstallerSession.readFromXml(in, mInternalCallback,
                                    mContext, mPm, mInstallThread.getLooper(), mStagingManager,
                                    mSessionsDir, this, mSilentUpdatePolicy);
                                    mSessionsDir, this, mSilentUpdatePolicy,
                                    mVerifierController);
                        } catch (Exception e) {
                            Slog.e(TAG, "Could not read session", e);
                            continue;
@@ -1037,7 +1041,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
                userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
                null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
                false, false, false, PackageManager.INSTALL_UNKNOWN, "", null);
                false, false, false, PackageManager.INSTALL_UNKNOWN, "", null,
                mVerifierController);

        synchronized (mSessions) {
            mSessions.put(sessionId, session);
@@ -1047,6 +1052,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        mCallbacks.notifySessionCreated(session.sessionId, session.userId);

        mSettingsWriteRequest.schedule();

        if (LOGD) {
            Slog.d(TAG, "Created session id=" + sessionId + " staged=" + params.isStaged);
        }
+119 −3
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAIL
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.verify.pkg.VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
import static android.os.Process.INVALID_UID;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static android.system.OsConstants.O_CREAT;
@@ -87,6 +88,7 @@ import android.content.pm.DataLoaderManager;
import android.content.pm.DataLoaderParams;
import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.FileSystemControlParcel;
import android.content.pm.Flags;
import android.content.pm.IDataLoader;
import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.IOnChecksumsReadyListener;
@@ -108,6 +110,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.ApkLite;
import android.content.pm.parsing.ApkLiteParseUtils;
@@ -115,6 +118,7 @@ import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.pkg.VerificationStatus;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
@@ -122,6 +126,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.icu.util.ULocale;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -133,6 +138,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.RevocableFileDescriptor;
@@ -190,6 +196,7 @@ import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.verify.pkg.VerifierController;

import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -218,6 +225,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private static final String TAG = "PackageInstallerSession";
@@ -404,6 +412,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     * Note all calls must be done outside {@link #mLock} to prevent lock inversion.
     */
    private final StagingManager mStagingManager;
    @NonNull private final VerifierController mVerifierController;

    final int sessionId;
    final int userId;
@@ -1156,7 +1165,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            boolean prepared, boolean committed, boolean destroyed, boolean sealed,
            @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
            boolean isFailed, boolean isApplied, int sessionErrorCode,
            String sessionErrorMessage, DomainSet preVerifiedDomains) {
            String sessionErrorMessage, DomainSet preVerifiedDomains,
            @NonNull VerifierController verifierController) {
        mCallback = callback;
        mContext = context;
        mPm = pm;
@@ -1165,6 +1175,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        mSilentUpdatePolicy = silentUpdatePolicy;
        mHandler = new Handler(looper, mHandlerCallback);
        mStagingManager = stagingManager;
        mVerifierController = verifierController;

        this.sessionId = sessionId;
        this.userId = userId;
@@ -1249,6 +1260,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                        "Archived installation can only use Streaming System DataLoader.");
            }
        }

        if (Flags.verificationService()) {
            // Start binding to the verification service, if not bound already.
            mVerifierController.bindToVerifierServiceIfNeeded(() -> pm.snapshotComputer(), userId);
            if (!TextUtils.isEmpty(params.appPackageName)) {
                mVerifierController.notifyPackageNameAvailable(params.appPackageName);
            }
        }
    }

    PackageInstallerHistoricalSession createHistoricalSession() {
@@ -2821,7 +2840,35 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            // since installation is in progress.
            activate();
        }
        if (Flags.verificationService()) {
            final Supplier<Computer> snapshotSupplier = mPm::snapshotComputer;
            if (mVerifierController.isVerifierInstalled(snapshotSupplier, userId)) {
                // TODO: extract shared library declarations
                final SigningInfo signingInfo;
                synchronized (mLock) {
                    signingInfo = new SigningInfo(mSigningDetails);
                }
                // Send the request to the verifier and wait for its response before the rest of
                // the installation can proceed.
                if (!mVerifierController.startVerificationSession(snapshotSupplier, userId,
                        sessionId, params.appPackageName, Uri.fromFile(stageDir), signingInfo,
                        /* declaredLibraries= */null, /* extensionParams= */ null,
                        new VerifierCallback(), /* retry= */ false)) {
                    // A verifier is installed but cannot be connected. Installation disallowed.
                    onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
                            "A verifier agent is available on device but cannot be connected.");
                }
            } else {
                // Verifier is not installed. Let the installation pass for now.
                resumeVerify();
            }
        } else {
            // New verification feature is not enabled. Proceed to the rest of the verification.
            resumeVerify();
        }
    }

    private void resumeVerify() {
        if (mVerificationInProgress) {
            Slog.w(TAG, "Verification is already in progress for session " + sessionId);
            return;
@@ -2856,6 +2903,66 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /**
     * Used for the VerifierController to report status back.
     */
    public class VerifierCallback {
        /**
         * Called by the VerifierController when the connection has failed.
         */
        public void onConnectionFailed() {
            mHandler.post(() -> {
                onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
                            "A verifier agent is available on device but cannot be connected.");
            });
        }
        /**
         * Called by the VerifierController when the verification request has timed out.
         */
        public void onTimeout() {
            mHandler.post(() -> {
                mVerifierController.notifyVerificationTimeout(sessionId);
                onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
                        "Verification timed out; missing a response from the verifier within the"
                                + " time limit");
            });
        }
        /**
         * Called by the VerifierController when the verification request has received a complete
         * response.
         */
        public void onVerificationCompleteReceived(@NonNull VerificationStatus statusReceived,
                @Nullable PersistableBundle extensionResponse) {
            // TODO: handle extension response
            mHandler.post(() -> {
                if (statusReceived.isVerified()) {
                    // Continue with the rest of the verification and installation.
                    resumeVerify();
                } else {
                    StringBuilder sb = new StringBuilder("Verifier rejected the installation");
                    if (!TextUtils.isEmpty(statusReceived.getFailureMessage())) {
                        sb.append(" with message: ").append(statusReceived.getFailureMessage());
                    }
                    onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
                            sb.toString());
                }
            });
        }
        /**
         * Called by the VerifierController when the verification request has received an incomplete
         * response.
         */
        public void onVerificationIncompleteReceived(int incompleteReason) {
            mHandler.post(() -> {
                if (incompleteReason == VERIFICATION_INCOMPLETE_UNKNOWN) {
                    // TODO: change this to a user confirmation and handle other incomplete reasons
                    onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
                            "Verification cannot be completed for unknown reasons.");
                }
            });
        }
    }

    private IntentSender getRemoteStatusReceiver() {
        synchronized (mLock) {
            return mRemoteStatusReceiver;
@@ -5369,6 +5476,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }
        } catch (InstallerException ignored) {
        }
        if (Flags.verificationService()
                && !TextUtils.isEmpty(params.appPackageName)
                && !isCommitted()) {
            // Only notify for the cancellation if the verification request has not
            // been sent out, which happens right after commit() is called.
            mVerifierController.notifyVerificationCancelled(
                    params.appPackageName);
        }
    }

    void dump(IndentingPrintWriter pw) {
@@ -5768,7 +5883,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            @NonNull PackageManagerService pm, Looper installerThread,
            @NonNull StagingManager stagingManager, @NonNull File sessionsDir,
            @NonNull PackageSessionProvider sessionProvider,
            @NonNull SilentUpdatePolicy silentUpdatePolicy)
            @NonNull SilentUpdatePolicy silentUpdatePolicy,
            @NonNull VerifierController verifierController)
            throws IOException, XmlPullParserException {
        final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
        final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -5972,6 +6088,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                installerUid, installSource, params, createdMillis, committedMillis, stageDir,
                stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
                childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
                sessionErrorCode, sessionErrorMessage, preVerifiedDomains);
                sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController);
    }
}
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.verify.pkg;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;

/**
 * This class keeps record of the current timeout status of a verification request.
 */
public final class VerificationStatusTracker {
    private final @CurrentTimeMillisLong long mStartTime;
    private @CurrentTimeMillisLong long mTimeoutTime;
    private final @CurrentTimeMillisLong long mMaxTimeoutTime;
    @NonNull
    private final VerifierController.Injector mInjector;
    // Record the package name associated with the verification result
    @NonNull
    private final String mPackageName;

    /**
     * By default, the timeout time is the default timeout duration plus the current time (when
     * the timer starts for a verification request). Both the default timeout time and the max
     * timeout time cannot be changed after the timer has started, but the actual timeout time
     * can be extended via {@link #extendTimeRemaining} to the maximum allowed.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
    public VerificationStatusTracker(@NonNull String packageName,
            long defaultTimeoutMillis, long maxExtendedTimeoutMillis,
            @NonNull VerifierController.Injector injector) {
        mPackageName = packageName;
        mStartTime = injector.getCurrentTimeMillis();
        mTimeoutTime = mStartTime + defaultTimeoutMillis;
        mMaxTimeoutTime = mStartTime + maxExtendedTimeoutMillis;
        mInjector = injector;
    }

    /**
     * Used by the controller to inform the verifier agent about the timestamp when the verification
     * request will timeout.
     */
    public @CurrentTimeMillisLong long getTimeoutTime() {
        return mTimeoutTime;
    }

    /**
     * Used by the controller to decide when to check for timeout again.
     * @return 0 if the timeout time has been reached, otherwise the remaining time in milliseconds
     * before the timeout is reached.
     */
    public @CurrentTimeMillisLong long getRemainingTime() {
        final long remainingTime = mTimeoutTime - mInjector.getCurrentTimeMillis();
        if (remainingTime < 0) {
            return 0;
        }
        return remainingTime;
    }

    /**
     * Used by the controller to extend the timeout duration of the verification request, upon
     * receiving the callback from the verifier agent.
     * @return the amount of time in millis that the timeout has been extended, subject to the max
     * amount allowed.
     */
    public long extendTimeRemaining(@CurrentTimeMillisLong long additionalMs) {
        if (mTimeoutTime + additionalMs > mMaxTimeoutTime) {
            additionalMs = mMaxTimeoutTime - mTimeoutTime;
        }
        mTimeoutTime += additionalMs;
        return additionalMs;
    }

    /**
     * Used by the controller to get the timeout status of the request.
     * @return False if the request still has some time left before timeout, otherwise return True.
     */
    public boolean isTimeout() {
        return mInjector.getCurrentTimeMillis() >= mTimeoutTime;
    }

    @NonNull
    public String getPackageName() {
        return mPackageName;
    }
}
+645 −0

File added.

Preview size limit exceeded, changes collapsed.

+6 −3
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.util.AtomicFile
import android.util.Slog
import android.util.Xml
import com.android.internal.os.BackgroundThread
import com.android.server.pm.verify.pkg.VerifierController
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import libcore.io.IoUtils
@@ -195,7 +196,8 @@ class PackageInstallerSessionTest {
            /* isApplied */ false,
            /* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
            /* stagedSessionErrorMessage */ "some error",
            /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
            /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
            /* VerifierController */ mock(VerifierController::class.java)
        )
    }

@@ -249,7 +251,8 @@ class PackageInstallerSessionTest {
                                mock(StagingManager::class.java),
                                mTmpDir,
                                mock(PackageSessionProvider::class.java),
                                mock(SilentUpdatePolicy::class.java)
                                mock(SilentUpdatePolicy::class.java),
                                mock(VerifierController::class.java)
                            )
                            ret.add(session)
                        } catch (e: Exception) {
Loading