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

Commit e6cb3104 authored by Bernardo Rufino's avatar Bernardo Rufino
Browse files

Allow custom toasts only if app has resumed activity

Bug linked is a vulnerability where an app starts an external activity
that it wants the user to interact with along with repeatedly posting
custom toasts with the intent of redressing the UI of the activity just
launched.

The activity that is being started is translucent, so the check for
IMPORTANCE_FOREGROUND wasn't working because activity manager was still
considering the process foreground since it had a visible activity.

Thus, changing our logic to only enable custom toasts in case the app
has a resumed activity. This has the following implications in the
cases below:
* Translucent activity on top: Block toasts
* Multi-window: Allow toasts
* Bubble: Allows when bubble is expanded (it's a resumed activity)
* SAW: Probably block, but fine since app can already do what they want
* onCreate(), onStart(), onResume(): Allow toasts
* onPause(), onStop(), onDestroy(): Block toasts

Note that custom toasts are deprecated and we haven't specified what
exactly "foreground" or "background" meant in this context, so we have
some flexibility in the implementation.

Bug: 115385786
Test: From an app start a translucent activity from another package and
      then try to post a toast, verify the toast is blocked.
Test: atest ToastUITest android.widget.cts.ToastTest
      android.widget.cts29.ToastTest android.server.wm.ToastTest
Change-Id: Ia434332e066f1ef2cd01e150b087f8e5117f1e63
parent f0e7d48f
Loading
Loading
Loading
Loading
+33 −5
Original line number Diff line number Diff line
@@ -272,6 +272,7 @@ import com.android.server.pm.PackageManagerService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;

import libcore.io.IoUtils;
@@ -403,6 +404,7 @@ public class NotificationManagerService extends SystemService {
    private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L;

    private IActivityManager mAm;
    private ActivityTaskManagerInternal mAtm;
    private ActivityManager mActivityManager;
    private IPackageManager mPackageManager;
    private PackageManager mPackageManagerClient;
@@ -1905,10 +1907,10 @@ public class NotificationManagerService extends SystemService {
            ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
            NotificationUsageStats usageStats, AtomicFile policyFile,
            ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
            UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
            IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps,
            UserManager userManager, NotificationHistoryManager historyManager,
            StatsManager statsManager) {
            ActivityTaskManagerInternal atm, UsageStatsManagerInternal appUsageStats,
            DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
            UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
            NotificationHistoryManager historyManager, StatsManager statsManager) {
        mHandler = handler;
        Resources resources = getContext().getResources();
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -1918,6 +1920,7 @@ public class NotificationManagerService extends SystemService {
        mAccessibilityManager =
                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
        mAm = am;
        mAtm = atm;
        mUgm = ugm;
        mUgmInternal = ugmInternal;
        mPackageManager = packageManager;
@@ -2104,6 +2107,7 @@ public class NotificationManagerService extends SystemService {
                        systemDir, "notification_policy.xml"), "notification-policy"),
                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
                getGroupHelper(), ActivityManager.getService(),
                LocalServices.getService(ActivityTaskManagerInternal.class),
                LocalServices.getService(UsageStatsManagerInternal.class),
                LocalServices.getService(DevicePolicyManagerInternal.class),
                UriGrantsManager.getService(),
@@ -2835,7 +2839,9 @@ public class NotificationManagerService extends SystemService {
                return;
            }

            if (callback != null && !appIsForeground && !isSystemToast && isCustom) {
            boolean isAppRenderedToast = (callback != null);
            if (isAppRenderedToast && isCustom && !isSystemToast
                    && !isPackageInForegroundForToast(pkg, callingUid)) {
                boolean block;
                long id = Binder.clearCallingIdentity();
                try {
@@ -2913,6 +2919,28 @@ public class NotificationManagerService extends SystemService {
            }
        }

        /**
         * Implementation note: Our definition of foreground for toasts is an implementation matter
         * and should strike a balance between functionality and anti-abuse effectiveness. We
         * currently worry about the following cases:
         * <ol>
         *     <li>App with fullscreen activity: Allow toasts
         *     <li>App behind translucent activity from other app: Block toasts
         *     <li>App in multi-window: Allow toasts
         *     <li>App with expanded bubble: Allow toasts
         *     <li>App posting toasts on onCreate(), onStart(), onResume(): Allow toasts
         *     <li>App posting toasts on onPause(), onStop(), onDestroy(): Block toasts
         * </ol>
         * Checking if the UID has any resumed activities satisfy use-cases above.
         *
         * <p>Checking if {@code mActivityManager.getUidImportance(callingUid) ==
         * IMPORTANCE_FOREGROUND} does not work because it considers the app in foreground if it has
         * any visible activities, failing case 2 in list above.
         */
        private boolean isPackageInForegroundForToast(String pkg, int callingUid) {
            return mAtm.hasResumedActivity(callingUid);
        }

        @Override
        public void cancelToast(String pkg, IBinder token) {
            Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token);
+5 −0
Original line number Diff line number Diff line
@@ -168,6 +168,11 @@ public abstract class ActivityTaskManagerInternal {
     */
    public abstract List<IBinder> getTopVisibleActivities();

    /**
     * Returns whether {@code uid} has any resumed activity.
     */
    public abstract boolean hasResumedActivity(int uid);

    /**
     * Notify listeners that contents are drawn for the first time on a single task display.
     *
+14 −0
Original line number Diff line number Diff line
@@ -6171,6 +6171,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
            }
        }

        @Override
        public boolean hasResumedActivity(int uid) {
            synchronized (mGlobalLock) {
                final ArraySet<WindowProcessController> processes = mProcessMap.getProcesses(uid);
                for (int i = 0, n = processes.size(); i < n; i++) {
                    final WindowProcessController process = processes.valueAt(i);
                    if (process.hasResumedActivity()) {
                        return true;
                    }
                }
            }
            return false;
        }

        @Override
        public int startActivitiesAsPackage(String packageName, @Nullable String featureId,
                int userId, Intent[] intents, Bundle bOptions) {
+10 −0
Original line number Diff line number Diff line
@@ -751,6 +751,16 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
        return false;
    }

    boolean hasResumedActivity() {
        for (int i = mActivities.size() - 1; i >= 0; --i) {
            final ActivityRecord activity = mActivities.get(i);
            if (activity.isState(RESUMED)) {
                return true;
            }
        }
        return false;
    }


    void updateIntentForHeavyWeightActivity(Intent intent) {
        if (mActivities.isEmpty()) {
+16 −16
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.server.notification;

import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.app.Notification.CATEGORY_CALL;
import static android.app.Notification.FLAG_AUTO_CANCEL;
@@ -41,8 +40,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -167,6 +164,7 @@ import com.android.server.notification.NotificationManagerService.NotificationAs
import com.android.server.notification.NotificationManagerService.NotificationListeners;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;

import org.junit.After;
@@ -253,6 +251,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    @Mock
    IActivityManager mAm;
    @Mock
    ActivityTaskManagerInternal mAtm;
    @Mock
    IUriGrantsManager mUgm;
    @Mock
    UriGrantsManagerInternal mUgmInternal;
@@ -442,7 +442,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager,
                mListeners, mAssistants, mConditionProviders,
                mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
                mGroupHelper, mAm, mAppUsageStats,
                mGroupHelper, mAm, mAtm, mAppUsageStats,
                mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                mAppOpsManager, mUm, mHistoryManager, mStatsManager);
        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
@@ -4632,8 +4632,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        mService.setPreferencesHelper(mPreferencesHelper);
        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);

        // this app is in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND);
        setAppInForegroundForToasts(mUid, true);

        // enqueue toast -> toast should still enqueue
        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
@@ -4651,8 +4650,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
                .thenReturn(false);

        // this app is NOT in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_NONE);
        setAppInForegroundForToasts(mUid, false);

        // enqueue toast -> no toasts enqueued
        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
@@ -4670,8 +4668,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
                .thenReturn(false);

        // this app is in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_FOREGROUND);
        setAppInForegroundForToasts(mUid, true);

        // enqueue toast -> toast should still enqueue
        ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(),
@@ -4689,8 +4686,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
                .thenReturn(false);

        // this app is NOT in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_NONE);
        setAppInForegroundForToasts(mUid, false);

        // enqueue toast -> toast should still enqueue
        ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(),
@@ -4748,8 +4744,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        mService.setPreferencesHelper(mPreferencesHelper);
        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);

        // this app is NOT in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE);
        setAppInForegroundForToasts(mUid, false);

        // enqueue toast -> no toasts enqueued
        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
@@ -4771,8 +4766,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        mService.setPreferencesHelper(mPreferencesHelper);
        when(mPreferencesHelper.getImportance(testPackage, mUid)).thenReturn(IMPORTANCE_NONE);

        // this app is NOT in the foreground
        when(mActivityManager.getUidImportance(mUid)).thenReturn(IMPORTANCE_GONE);
        setAppInForegroundForToasts(mUid, false);

        // enqueue toast -> system toast can still be enqueued
        ((INotificationManager) mService.mService).enqueueToast(testPackage, new Binder(),
@@ -4780,6 +4774,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertEquals(1, mService.mToastQueue.size());
    }

    private void setAppInForegroundForToasts(int uid, boolean inForeground) {
        int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE;
        when(mActivityManager.getUidImportance(mUid)).thenReturn(importance);
        when(mAtm.hasResumedActivity(uid)).thenReturn(inForeground);
    }

    @Test
    public void testOnPanelRevealedAndHidden() {
        int items = 5;
Loading