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

Commit 4c1deb59 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Validate pending intents"

parents c7229847 37880517
Loading
Loading
Loading
Loading
+100 −63
Original line number Diff line number Diff line
@@ -421,6 +421,7 @@ public class NotificationManagerService extends SystemService {
    private IActivityManager mAm;
    private ActivityTaskManagerInternal mAtm;
    private ActivityManager mActivityManager;
    private ActivityManagerInternal mAmi;
    private IPackageManager mPackageManager;
    private PackageManager mPackageManagerClient;
    AudioManager mAudioManager;
@@ -1897,7 +1898,7 @@ public class NotificationManagerService extends SystemService {
            DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
            UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
            NotificationHistoryManager historyManager, StatsManager statsManager,
            TelephonyManager telephonyManager) {
            TelephonyManager telephonyManager, ActivityManagerInternal ami) {
        mHandler = handler;
        Resources resources = getContext().getResources();
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -1919,6 +1920,7 @@ public class NotificationManagerService extends SystemService {
        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
        mCompanionManager = companionManager;
        mActivityManager = activityManager;
        mAmi = ami;
        mDeviceIdleManager = getContext().getSystemService(DeviceIdleManager.class);
        mDpm = dpm;
        mUm = userManager;
@@ -2197,7 +2199,8 @@ public class NotificationManagerService extends SystemService {
                new NotificationHistoryManager(getContext(), handler),
                mStatsManager = (StatsManager) getContext().getSystemService(
                        Context.STATS_MANAGER),
                getContext().getSystemService(TelephonyManager.class));
                getContext().getSystemService(TelephonyManager.class),
                LocalServices.getService(ActivityManagerInternal.class));

        publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
                DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -5276,13 +5279,13 @@ public class NotificationManagerService extends SystemService {
                        notificationRecord.getIsAppImportanceLocked());
                summaries.put(pkg, summarySbn.getKey());
            }
        }
            if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
                    summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
                    true)) {
                mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground));
            }
        }
    }

    private String disableNotificationEffects(NotificationRecord record) {
        if (mDisableNotificationEffects) {
@@ -6019,13 +6022,17 @@ public class NotificationManagerService extends SystemService {
                + " cannot post for pkg " + targetPkg + " in user " + userId);
    }

    public boolean hasFlag(final int flags, final int flag) {
        return (flags & flag) != 0;
    }
    /**
     * Checks if a notification can be posted. checks rate limiter, snooze helper, and blocking.
     *
     * Has side effects.
     */
    private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
    boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
            NotificationRecord r, boolean isAutogroup) {
        Notification n = r.getNotification();
        final String pkg = r.getSbn().getPackageName();
        final boolean isSystemNotification =
                isUidSystemOrPhone(uid) || ("android".equals(pkg));
@@ -6034,7 +6041,6 @@ public class NotificationManagerService extends SystemService {
        // Limit the number of notifications that any given package except the android
        // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.
        if (!isSystemNotification && !isNotificationFromListener) {
            synchronized (mNotificationLock) {
            final int callingUid = Binder.getCallingUid();
            if (mNotificationsByKey.get(r.getSbn().getKey()) == null
                    && isCallerInstantApp(callingUid, userId)) {
@@ -6065,7 +6071,7 @@ public class NotificationManagerService extends SystemService {
            }

            // limit the number of non-fgs outstanding notificationrecords an app can have
                if (!r.getNotification().isForegroundService()) {
            if (!n.isForegroundService()) {
                int count = getNotificationCountLocked(pkg, userId, id, tag);
                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                    mUsageStats.registerOverCountQuota(pkg);
@@ -6075,9 +6081,41 @@ public class NotificationManagerService extends SystemService {
                }
            }
        }

        // bubble or inline reply that's immutable?
        if (n.getBubbleMetadata() != null
                && n.getBubbleMetadata().getIntent() != null
                && hasFlag(mAmi.getPendingIntentFlags(
                        n.getBubbleMetadata().getIntent().getTarget()),
                        PendingIntent.FLAG_IMMUTABLE)) {
            throw new IllegalArgumentException(r.getKey() + " Not posted."
                    + " PendingIntents attached to bubbles must be mutable");
        }

        if (n.actions != null) {
            for (Notification.Action action : n.actions) {
                if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
                        && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
                        PendingIntent.FLAG_IMMUTABLE)) {
                    throw new IllegalArgumentException(r.getKey() + " Not posted."
                            + " PendingIntents attached to actions with remote"
                            + " inputs must be mutable");
                }
            }
        }

        if (r.getSystemGeneratedSmartActions() != null) {
            for (Notification.Action action : r.getSystemGeneratedSmartActions()) {
                if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
                        && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
                        PendingIntent.FLAG_IMMUTABLE)) {
                    throw new IllegalArgumentException(r.getKey() + " Not posted."
                            + " PendingIntents attached to contextual actions with remote inputs"
                            + " must be mutable");
                }
            }
        }

        synchronized (mNotificationLock) {
        // snoozed apps
        if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
            MetricsLogger.action(r.getLogMaker()
@@ -6099,7 +6137,6 @@ public class NotificationManagerService extends SystemService {
        if (isBlocked(r, mUsageStats)) {
            return false;
        }
        }

        return true;
    }
+169 −4
Original line number Diff line number Diff line
@@ -43,6 +43,9 @@ 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.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -110,6 +113,7 @@ import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IIntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -248,6 +252,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    Resources mResources;
    @Mock
    RankingHandler mRankingHandler;
    @Mock
    ActivityManagerInternal mAmi;

    @Mock
    IIntentSender pi1;

    private static final int MAX_POST_DELAY = 1000;

@@ -392,7 +401,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {

        DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class);
        when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L);
        ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class);

        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
        LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
@@ -403,7 +411,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        LocalServices.removeServiceForTest(DeviceIdleInternal.class);
        LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
        LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal);
        LocalServices.addService(ActivityManagerInternal.class, mAmi);

        doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());

@@ -477,7 +485,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
                mGroupHelper, mAm, mAtm, mAppUsageStats,
                mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                mAppOpsManager, mUm, mHistoryManager, mStatsManager,
                mock(TelephonyManager.class));
                mock(TelephonyManager.class), mAmi);
        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);

        mService.setAudioManager(mAudioManager);
@@ -674,7 +682,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        }
        Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                .setContentTitle("foo")
                .setSmallIcon(android.R.drawable.sym_def_app_icon);
                .setSmallIcon(android.R.drawable.sym_def_app_icon)
                .addAction(new Notification.Action.Builder(null, "test", null).build());
        if (extender != null) {
            nb.extend(extender);
        }
@@ -810,6 +819,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        PendingIntent pendingIntent = mock(PendingIntent.class);
        Intent intent = mock(Intent.class);
        when(pendingIntent.getIntent()).thenReturn(intent);
        when(pendingIntent.getTarget()).thenReturn(pi1);

        ActivityInfo info = new ActivityInfo();
        info.resizeMode = RESIZE_MODE_RESIZEABLE;
@@ -7134,4 +7144,159 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        inOrder.verify(parent).recordDismissalSentiment(anyInt());
        inOrder.verify(child).recordDismissalSentiment(anyInt());
    }

    @Test
    public void testImmutableBubbleIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(pi1))
                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
        NotificationRecord r = generateMessageBubbleNotifRecord(true,
                mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false);
        try {
            mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
                    r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());

            waitForIdle();
            fail("Allowed a bubble with an immutable intent to be posted");
        } catch (IllegalArgumentException e) {
            // good
        }
    }

    @Test
    public void testMutableBubbleIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(pi1))
                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
        NotificationRecord r = generateMessageBubbleNotifRecord(true,
                mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false);

        mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
                r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());

        waitForIdle();
        StatusBarNotification[] notifs =
                mBinderService.getActiveNotifications(r.getSbn().getPackageName());
        assertEquals(1, notifs.length);
    }

    @Test
    public void testImmutableDirectReplyActionIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
        NotificationRecord r = generateMessageBubbleNotifRecord(false,
                mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false);
        try {
            mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
                    r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());

            waitForIdle();
            fail("Allowed a direct reply with an immutable intent to be posted");
        } catch (IllegalArgumentException e) {
            // good
        }
    }

    @Test
    public void testMutableDirectReplyActionIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
        NotificationRecord r = generateMessageBubbleNotifRecord(false,
                mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false);
        mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
                r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());

        waitForIdle();
        StatusBarNotification[] notifs =
                mBinderService.getActiveNotifications(r.getSbn().getPackageName());
        assertEquals(1, notifs.length);
    }

    @Test
    public void testImmutableDirectReplyContextualActionIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);

        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
        ArrayList<Notification.Action> extraAction = new ArrayList<>();
        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
                inputIntent).addRemoteInput(remoteInput)
                .build();
        extraAction.add(replyAction);
        Bundle signals = new Bundle();
        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
                r.getUser());
        r.addAdjustment(adjustment);
        r.applyAdjustments();

        try {
            mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
                    r.getSbn().getTag(), r,false);
            fail("Allowed a contextual direct reply with an immutable intent to be posted");
        } catch (IllegalArgumentException e) {
            // good
        }
    }

    @Test
    public void testMutableDirectReplyContextualActionIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
        ArrayList<Notification.Action> extraAction = new ArrayList<>();
        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
                inputIntent).addRemoteInput(remoteInput)
                .build();
        extraAction.add(replyAction);
        Bundle signals = new Bundle();
        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
                r.getUser());
        r.addAdjustment(adjustment);
        r.applyAdjustments();

        mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
                r.getSbn().getTag(), r,false);
    }

    @Test
    public void testImmutableActionIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);

        mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
                r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());

        waitForIdle();
        StatusBarNotification[] notifs =
                mBinderService.getActiveNotifications(r.getSbn().getPackageName());
        assertEquals(1, notifs.length);
    }

    @Test
    public void testImmutableContextualActionIntent() throws Exception {
        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
        ArrayList<Notification.Action> extraAction = new ArrayList<>();
        extraAction.add(new Notification.Action(0, "hello", null));
        Bundle signals = new Bundle();
        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
                r.getUser());
        r.addAdjustment(adjustment);
        r.applyAdjustments();

        mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
                    r.getSbn().getTag(), r,false);
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IUriGrantsManager;
@@ -154,7 +155,8 @@ public class RoleObserverTest extends UiServiceTestCase {
                    mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
                    mock(UriGrantsManagerInternal.class),
                    mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
                    mock(StatsManager.class), mock(TelephonyManager.class));
                    mock(StatsManager.class), mock(TelephonyManager.class),
                    mock(ActivityManagerInternal.class));
        } catch (SecurityException e) {
            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                throw e;