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

Commit 637e05ee authored by Felipe Leme's avatar Felipe Leme
Browse files

Further improvements when app forges package info for autofill:

- Dont create a session if the component is not owned by the calling UID.
- Log metrics for forged attempts.
- Avoid possible NPEs on AutofillManager when context or client is null.

Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases -t android.autofillservice.cts.VirtualContainerActivityTest#testAppCannotFakePackageName
Test: cts-tradefed run commandAndExit cts-dev -m CtsAutoFillServiceTestCases

Bug: 69981710

Change-Id: I9695bc046f3eb8aeecfe44f80fd0366f68b2c635
parent 6798bc30
Loading
Loading
Loading
Loading
+25 −23
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.view.View;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -428,7 +429,7 @@ public final class AutofillManager {
     * @hide
     */
    public AutofillManager(Context context, IAutoFillManager service) {
        mContext = context;
        mContext = Preconditions.checkNotNull(context, "context cannot be null");
        mService = service;
    }

@@ -457,7 +458,7 @@ public final class AutofillManager {
            if (mSessionId != NO_SESSION) {
                ensureServiceClientAddedIfNeededLocked();

                final AutofillClient client = getClientLocked();
                final AutofillClient client = getClient();
                if (client != null) {
                    try {
                        final boolean sessionWasRestored = mService.restoreSession(mSessionId,
@@ -1076,7 +1077,8 @@ public final class AutofillManager {
        }
    }

    private AutofillClient getClientLocked() {
    // Note: don't need to use locked suffix because mContext is final.
    private AutofillClient getClient() {
        final AutofillClient client = mContext.getAutofillClient();
        if (client == null && sDebug) {
            Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
@@ -1139,16 +1141,16 @@ public final class AutofillManager {
            return;
        }
        try {
            final AutofillClient client = getClientLocked();
            final AutofillClient client = getClient();
            if (client == null) return; // NOTE: getClient() already logd it..

            mSessionId = mService.startSession(mContext.getActivityToken(),
                    mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                    mCallback != null, flags, client.getComponentName());
            if (mSessionId != NO_SESSION) {
                mState = STATE_ACTIVE;
            }
            if (client != null) {
            client.autofillCallbackResetableStateAvailable();
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -1200,7 +1202,9 @@ public final class AutofillManager {

        try {
            if (restartIfNecessary) {
                final AutofillClient client = getClientLocked();
                final AutofillClient client = getClient();
                if (client == null) return; // NOTE: getClient() already logd it..

                final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
                        mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
                        mCallback != null, flags, client.getComponentName(), mSessionId, action);
@@ -1208,10 +1212,8 @@ public final class AutofillManager {
                    if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
                    mSessionId = newId;
                    mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
                    if (client != null) {
                    client.autofillCallbackResetableStateAvailable();
                }
                }
            } else {
                mService.updateSession(mSessionId, id, bounds, value, action, flags,
                        mContext.getUserId());
@@ -1223,7 +1225,7 @@ public final class AutofillManager {
    }

    private void ensureServiceClientAddedIfNeededLocked() {
        if (getClientLocked() == null) {
        if (getClient() == null) {
            return;
        }

@@ -1306,7 +1308,7 @@ public final class AutofillManager {
        AutofillCallback callback = null;
        synchronized (mLock) {
            if (mSessionId == sessionId) {
                AutofillClient client = getClientLocked();
                AutofillClient client = getClient();

                if (client != null) {
                    if (client.autofillCallbackRequestShowFillUi(anchor, width, height,
@@ -1331,7 +1333,7 @@ public final class AutofillManager {
            Intent fillInIntent) {
        synchronized (mLock) {
            if (sessionId == mSessionId) {
                AutofillClient client = getClientLocked();
                final AutofillClient client = getClient();
                if (client != null) {
                    client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent);
                }
@@ -1396,7 +1398,7 @@ public final class AutofillManager {
                return;
            }

            final AutofillClient client = getClientLocked();
            final AutofillClient client = getClient();
            if (client == null) {
                return;
            }
@@ -1573,7 +1575,7 @@ public final class AutofillManager {
            // 1. If local and remote session id are off sync the UI would be stuck shown
            // 2. There is a race between the user state being destroyed due the fill
            //    service being uninstalled and the UI being dismissed.
            AutofillClient client = getClientLocked();
            AutofillClient client = getClient();
            if (client != null) {
                if (client.autofillCallbackRequestHideFillUi() && mCallback != null) {
                    callback = mCallback;
@@ -1603,7 +1605,7 @@ public final class AutofillManager {

        AutofillCallback callback = null;
        synchronized (mLock) {
            if (mSessionId == sessionId && getClientLocked() != null) {
            if (mSessionId == sessionId && getClient() != null) {
                callback = mCallback;
            }
        }
@@ -1660,7 +1662,7 @@ public final class AutofillManager {
     * @return The view or {@code null} if view was not found
     */
    private View findView(@NonNull AutofillId autofillId) {
        final AutofillClient client = getClientLocked();
        final AutofillClient client = getClient();

        if (client == null) {
            return null;
@@ -1694,7 +1696,7 @@ public final class AutofillManager {
        pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
        pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
        pw.print(pfx); pw.print("context: "); pw.println(mContext);
        pw.print(pfx); pw.print("client: "); pw.println(getClientLocked());
        pw.print(pfx); pw.print("client: "); pw.println(getClient());
        pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
        pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
        pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1741,7 +1743,7 @@ public final class AutofillManager {
    }

    private void post(Runnable runnable) {
        final AutofillClient client = getClientLocked();
        final AutofillClient client = getClient();
        if (client == null) {
            if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
            return;
@@ -1824,7 +1826,7 @@ public final class AutofillManager {
         * @param trackedIds The views to be tracked
         */
        TrackedViews(@Nullable AutofillId[] trackedIds) {
            final AutofillClient client = getClientLocked();
            final AutofillClient client = getClient();
            if (trackedIds != null && client != null) {
                final boolean[] isVisible;

@@ -1865,7 +1867,7 @@ public final class AutofillManager {
         * @param isVisible visible if the view is visible in the view hierarchy.
         */
        void notifyViewVisibilityChanged(@NonNull AutofillId id, boolean isVisible) {
            AutofillClient client = getClientLocked();
            AutofillClient client = getClient();

            if (sDebug) {
                Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible="
@@ -1902,7 +1904,7 @@ public final class AutofillManager {
        void onVisibleForAutofillLocked() {
            // The visibility of the views might have changed while the client was not be visible,
            // hence update the visibility state for all views.
            AutofillClient client = getClientLocked();
            AutofillClient client = getClient();
            ArraySet<AutofillId> updatedVisibleTrackedIds = null;
            ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
            if (client != null) {
+13 −0
Original line number Diff line number Diff line
@@ -4138,6 +4138,19 @@ message MetricsEvent {
    // OS: O
    FIELD_NOTIFICATION_GROUP_SUMMARY = 947;

    // An app attempted to forge a different component name in the AssisStructure that would be
    // passed to the autofill service.
    // OS: O (security patch)
    // Package: Real package of the app being autofilled
    // Tag FIELD_AUTOFILL_SERVICE: Package of the autofill service that processed the request
    // TAG FIELD_AUTOFILL_FORGED_COMPONENT_NAME: Component name being forged
    AUTOFILL_FORGED_COMPONENT_ATTEMPT = 948;

    // FIELD - The component that an app tried tro forged.
    // Type: string
    // OS: O (security patch)
    FIELD_AUTOFILL_FORGED_COMPONENT_NAME = 949;

    // ---- End O Constants, all O constants go above this line ----

    // OPEN: Settings > System > Languages & input > Advanced > Lift to open camera
+32 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -43,6 +44,7 @@ import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.autofill.AutofillService;
@@ -463,6 +465,8 @@ final class AutofillManagerServiceImpl {
            sessionId = sRandom.nextInt();
        } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);

        assertCallerLocked(componentName);

        final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
                sessionId, uid, activityToken, appCallbackToken, hasCallback,
                mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName);
@@ -471,6 +475,34 @@ final class AutofillManagerServiceImpl {
        return newSession;
    }

    /**
     * Asserts the component is owned by the caller.
     */
    private void assertCallerLocked(@NonNull ComponentName componentName) {
        final PackageManager pm = mContext.getPackageManager();
        final int callingUid = Binder.getCallingUid();
        final int packageUid;
        try {
            packageUid = pm.getPackageUidAsUser(componentName.getPackageName(),
                    UserHandle.getCallingUserId());
        } catch (NameNotFoundException e) {
            throw new SecurityException("Could not verify UID for " + componentName);
        }
        if (callingUid != packageUid) {
            final String[] packages = pm.getPackagesForUid(callingUid);
            final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
            Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
                    + ") passed component (" + componentName + ") owned by UID " + packageUid);
            mMetricsLogger.write(
                    Helper.newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT,
                            callingPackage, getServicePackageName())
                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
                            componentName == null ? "null" : componentName.flattenToShortString()));

            throw new SecurityException("Invalid component: " + componentName);
        }
    }

    /**
     * Restores a session after an activity was temporarily destroyed.
     *
+8 −3
Original line number Diff line number Diff line
@@ -236,10 +236,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                structure.ensureData();

                // Sanitize structure before it's sent to service.
                if (!mComponentName.equals(structure.getActivityComponent())) {
                final ComponentName componentNameFromApp = structure.getActivityComponent();
                if (!mComponentName.equals(componentNameFromApp)) {
                    Slog.w(TAG, "Activity " + mComponentName + " forged different component on "
                            + "AssistStructure: " + structure.getActivityComponent());
                            + "AssistStructure: " + componentNameFromApp);
                    structure.setActivityComponent(mComponentName);
                    mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT)
                            .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
                                    componentNameFromApp == null ? "null"
                                            : componentNameFromApp.flattenToShortString()));
                }
                structure.sanitizeForParceling(true);

@@ -1675,7 +1680,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
                break;
            case ACTION_VIEW_ENTERED:
                if (sVerbose && virtualBounds != null) {
                    Slog.w(TAG, "entered on virtual child " + id + ": " + virtualBounds);
                    Slog.v(TAG, "entered on virtual child " + id + ": " + virtualBounds);
                }
                requestNewFillResponseIfNecessaryLocked(id, viewState, flags);