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

Commit ec065a83 authored by Rhed Jao's avatar Rhed Jao
Browse files

Throttle the repeated silent installation requests

To avoid a non-system installer repeatedly silent updates and
'denial-of-service' against another app on the device. This CL
tracks for all the silent updated installs where the
`requireUserAction' is not required. And fall back to user
action required if a repeated silent update is requested within
the throttle time.

Bug: 185878964
Test: atest SilentUpdateHostsideTests
Test: atest StagingManagerTest
Test: atest PackageInstallerSessionTest
Change-Id: I4ea287bf66d8661eec2676b604f9109f6f9add28
Merged-In: I4ea287bf66d8661eec2676b604f9109f6f9add28
parent c56f67e7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -61,4 +61,6 @@ interface IPackageInstaller {
    void setPermissionsResult(int sessionId, boolean accepted);

    void bypassNextStagedInstallerCheck(boolean value);

    void setAllowUnlimitedSilentUpdates(String installerPackageName);
}
+22 −5
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -194,6 +195,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
    @GuardedBy("mSessions")
    private final SparseBooleanArray mLegacySessions = new SparseBooleanArray();

    /** Policy for allowing a silent update. */
    private final SilentUpdatePolicy mSilentUpdatePolicy = new SilentUpdatePolicy();

    private static final FilenameFilter sStageFilter = new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
@@ -409,7 +413,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                        try {
                            session = PackageInstallerSession.readFromXml(in, mInternalCallback,
                                    mContext, mPm, mInstallThread.getLooper(), mStagingManager,
                                    mSessionsDir, this);
                                    mSessionsDir, this, mSilentUpdatePolicy);
                        } catch (Exception e) {
                            Slog.e(TAG, "Could not read session", e);
                            continue;
@@ -755,10 +759,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                originatingPackageName, requestedInstallerPackageName,
                installerAttributionTag);
        session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                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,
                SessionInfo.STAGED_SESSION_NO_ERROR, "");
                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, SessionInfo.STAGED_SESSION_NO_ERROR, "");

        synchronized (mSessions) {
            mSessions.put(sessionId, session);
@@ -1087,6 +1091,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        mBypassNextStagedInstallerCheck = value;
    }

    /**
     * Set an installer to allow for the unlimited silent updates.
     */
    @Override
    public void setAllowUnlimitedSilentUpdates(@Nullable String installerPackageName) {
        if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
            throw new SecurityException("Caller not allowed to unlimite silent updates");
        }
        mSilentUpdatePolicy.setAllowUnlimitedSilentUpdates(installerPackageName);
    }

    private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
            int installerUid) {
        int count = 0;
@@ -1369,8 +1384,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
            pw.println("Legacy install sessions:");
            pw.increaseIndent();
            pw.println(mLegacySessions.toString());
            pw.println();
            pw.decreaseIndent();
        }
        mSilentUpdatePolicy.dump(pw);
    }

    class InternalCallback {
+33 −12
Original line number Diff line number Diff line
@@ -270,6 +270,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private final PackageManagerService mPm;
    private final Handler mHandler;
    private final PackageSessionProvider mSessionProvider;
    private final SilentUpdatePolicy mSilentUpdatePolicy;
    /**
     * Note all calls must be done outside {@link #mLock} to prevent lock inversion.
     */
@@ -995,8 +996,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    }

    public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
            Context context, PackageManagerService pm,
            PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
            Context context, PackageManagerService pm, PackageSessionProvider sessionProvider,
            SilentUpdatePolicy silentUpdatePolicy, Looper looper, StagingManager stagingManager,
            int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
            SessionParams params, long createdMillis, long committedMillis,
            File stageDir, String stageCid, InstallationFile[] files,
@@ -1009,6 +1010,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        mContext = context;
        mPm = pm;
        mSessionProvider = sessionProvider;
        mSilentUpdatePolicy = silentUpdatePolicy;
        mHandler = new Handler(looper, mHandlerCallback);
        mStagingManager = stagingManager;

@@ -2384,6 +2386,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            } // else, we'll wait until we parse to determine if we need to
        }

        boolean silentUpdatePolicyEnforceable = false;
        synchronized (mLock) {
            if (mRelinquished) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
@@ -2408,12 +2411,28 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                extractNativeLibraries(
                        mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());

                if (userActionRequirement == USER_ACTION_PENDING_APK_PARSING
                        && (result.getTargetSdk() < Build.VERSION_CODES.Q)) {
                if (userActionRequirement == USER_ACTION_PENDING_APK_PARSING) {
                    if (result.getTargetSdk() < Build.VERSION_CODES.Q) {
                        sendPendingUserActionIntent();
                        return null;
                    }
                    if (params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED) {
                        silentUpdatePolicyEnforceable = true;
                    }
                }
            }
        }
        if (silentUpdatePolicyEnforceable) {
            if (!mSilentUpdatePolicy.isSilentUpdateAllowed(
                    getInstallerPackageName(), getPackageName())) {
                // Fall back to the non-silent update if a repeated installation is invoked within
                // the throttle time.
                sendPendingUserActionIntent();
                return null;
            }
            mSilentUpdatePolicy.track(getInstallerPackageName(), getPackageName());
        }
        synchronized (mLock) {
            return makeVerificationParamsLocked();
        }
    }
@@ -4569,7 +4588,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
            @NonNull PackageManagerService pm, Looper installerThread,
            @NonNull StagingManager stagingManager, @NonNull File sessionsDir,
            @NonNull PackageSessionProvider sessionProvider)
            @NonNull PackageSessionProvider sessionProvider,
            @NonNull SilentUpdatePolicy silentUpdatePolicy)
            throws IOException, XmlPullParserException {
        final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
        final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -4743,10 +4763,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {

        InstallSource installSource = InstallSource.create(installInitiatingPackageName,
                installOriginatingPackageName, installerPackageName, installerAttributionTag);
        return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread,
                stagingManager, sessionId, userId, installerUid, installSource, params,
                createdMillis, committedMillis, stageDir, stageCid, fileArray, checksumsMap,
                prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId,
                isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
        return new PackageInstallerSession(callback, context, pm, sessionProvider,
                silentUpdatePolicy, installerThread, stagingManager, sessionId, userId,
                installerUid, installSource, params, createdMillis, committedMillis, stageDir,
                stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
                childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
                stagedSessionErrorCode, stagedSessionErrorMessage);
    }
}
+35 −0
Original line number Diff line number Diff line
@@ -304,6 +304,8 @@ class PackageManagerShellCommand extends ShellCommand {
                    return runLogVisibility();
                case "bypass-staged-installer-check":
                    return runBypassStagedInstallerCheck();
                case "allow-unlimited-silent-updates":
                    return runAllowUnlimitedSilentUpdates();
                default: {
                    Boolean domainVerificationResult =
                            mDomainVerificationShell.runCommand(this, cmd);
@@ -2978,6 +2980,33 @@ class PackageManagerShellCommand extends ShellCommand {
        }
    }

    private int runAllowUnlimitedSilentUpdates() {
        final PrintWriter pw = getOutPrintWriter();
        String opt;
        boolean reset = false;
        while ((opt = getNextOption()) != null) {
            switch (opt) {
                case "--reset":
                    reset = true;
                    break;
                default:
                    pw.println("Error: Unknown option: " + opt);
                    return -1;
            }
        }

        final String installerPackageName = reset ? null : getNextArgRequired();
        try {
            mInterface.getPackageInstaller().setAllowUnlimitedSilentUpdates(installerPackageName);
        } catch (RemoteException e) {
            pw.println("Failure ["
                    + e.getClass().getName() + " - "
                    + e.getMessage() + "]");
            return -1;
        }
        return 1;
    }

    private static String checkAbiArgument(String abi) {
        if (TextUtils.isEmpty(abi)) {
            throw new IllegalArgumentException("Missing ABI argument");
@@ -3792,6 +3821,12 @@ class PackageManagerShellCommand extends ShellCommand {
        pw.println("      --enable: turn on debug logging (default)");
        pw.println("      --disable: turn off debug logging");
        pw.println("");
        pw.println("  allow-unlimited-silent-updates (--reset | <INSTALLER>)");
        pw.println("    Allows unlimited silent updated installation requests from the installer");
        pw.println("    without the throttle time.");
        pw.println("      --reset: clear the allowed installer and tracks of silent updates in");
        pw.println("        the system.");
        pw.println("");
        mDomainVerificationShell.printHelp(pw);
        pw.println("");
        Intent.printIntentArgsHelp(pw , "");
+146 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.Pair;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;

import java.util.concurrent.TimeUnit;

/**
 * Tracks for the installer package name and installing app package name of silent updates.
 * This class is used to throttle repeated silent updates of the same installer and application
 * in the {@link PackageInstallerSession}.
 */
public class SilentUpdatePolicy {
    // A throttle time to prevent the installer from silently updating the same app repeatedly.
    private static final long SILENT_UPDATE_THROTTLE_TIME_MS = TimeUnit.SECONDS.toMillis(30);

    // Map to the uptime timestamp for each installer and app of the silent update.
    @GuardedBy("mSilentUpdateInfos")
    private final ArrayMap<Pair<String, String>, Long> mSilentUpdateInfos = new ArrayMap<>();

    // An installer allowed for the unlimited silent updates within the throttle time
    @GuardedBy("mSilentUpdateInfos")
    private String mAllowUnlimitedSilentUpdatesInstaller;

    /**
     * Checks if the silent update is allowed by the given installer and app package name.
     *
     * @param installerPackageName The installer package name to check
     * @param packageName The package name which is installing
     * @return true if the silent update is allowed.
     */
    public boolean isSilentUpdateAllowed(@Nullable String installerPackageName,
            @NonNull String packageName) {
        if (installerPackageName == null) {
            // Always true for the installer from Shell
            return true;
        }
        final long lastSilentUpdatedMs = getTimestampMs(installerPackageName, packageName);
        return SystemClock.uptimeMillis() - lastSilentUpdatedMs > SILENT_UPDATE_THROTTLE_TIME_MS;
    }

    /**
     * Adding track for the installer package name and installing app of a silent update. This is
     * used to determine whether a silent update is allowed.
     *
     * @param installerPackageName The installer package name
     * @param packageName The package name which is installing
     */
    public void track(@Nullable String installerPackageName, @NonNull String packageName) {
        if (installerPackageName == null) {
            // No need to track the installer from Shell.
            return;
        }
        synchronized (mSilentUpdateInfos) {
            if (mAllowUnlimitedSilentUpdatesInstaller != null
                    && mAllowUnlimitedSilentUpdatesInstaller.equals(installerPackageName)) {
                return;
            }
            final long uptime = SystemClock.uptimeMillis();
            pruneLocked(uptime);

            final Pair<String, String> key = Pair.create(installerPackageName, packageName);
            mSilentUpdateInfos.put(key, uptime);
        }
    }

    /**
     * Set an installer to allow for the unlimited silent updates. Reset the tracker if the
     * installer package name is <code>null</code>.
     */
    void setAllowUnlimitedSilentUpdates(@Nullable String installerPackageName) {
        synchronized (mSilentUpdateInfos) {
            if (installerPackageName == null) {
                mSilentUpdateInfos.clear();
            }
            mAllowUnlimitedSilentUpdatesInstaller = installerPackageName;
        }
    }

    private void pruneLocked(long uptime) {
        final int size = mSilentUpdateInfos.size();
        for (int i = size - 1; i >= 0; i--) {
            final long lastSilentUpdatedMs = mSilentUpdateInfos.valueAt(i);
            if (uptime - lastSilentUpdatedMs > SILENT_UPDATE_THROTTLE_TIME_MS) {
                mSilentUpdateInfos.removeAt(i);
            }
        }
    }

    /**
     * Get the timestamp by the given installer and app package name. {@code -1} is returned if not
     * exist.
     */
    private long getTimestampMs(@NonNull String installerPackageName, @NonNull String packageName) {
        final Pair<String, String> key = Pair.create(installerPackageName, packageName);
        final Long timestampMs;
        synchronized (mSilentUpdateInfos) {
            timestampMs = mSilentUpdateInfos.get(key);
        }
        return timestampMs != null ? timestampMs : -1;
    }

    void dump(IndentingPrintWriter pw) {
        synchronized (mSilentUpdateInfos) {
            if (mSilentUpdateInfos.isEmpty()) {
                return;
            }
            pw.println("Last silent updated Infos:");
            pw.increaseIndent();
            final int size = mSilentUpdateInfos.size();
            for (int i = 0; i < size; i++) {
                final Pair<String, String> key = mSilentUpdateInfos.keyAt(i);
                if (key == null) {
                    continue;
                }
                pw.printPair("installerPackageName", key.first);
                pw.printPair("packageName", key.second);
                pw.printPair("silentUpdatedMillis", mSilentUpdateInfos.valueAt(i));
                pw.println();
            }
            pw.decreaseIndent();
        }
    }
}
Loading