Skip to content

Commit eb7f3a6

Browse files
Valentin IftimeAndroid Build Coastguard Worker
authored andcommitted
Enforce persisted snoozed notifications limits
Prevent DoS attack that causes boot-looping by serializing a huge amount of snoozed notifications: - Check snooze limits for persisted notifications - Remove persisted group summary notification when in-memory counterpart is removed - Prevent unpriviledged API calls that allow 3P apps to snooze notifications with context/criterion Test: atest SnoozeHelperTest Test: atest NotificationManagerServiceTest Bug: 307948424 Bug: 308414141 (cherry picked from commit 965ff2d3c5487f72a77f6153ed8542cb2621d93c) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ade22bfdf6698cb97b4edc303e8952d6cc1a2f73) Merged-In: I3571fa9207b778def652130d3ca840183a9a8414 Change-Id: I3571fa9207b778def652130d3ca840183a9a8414
1 parent 3279205 commit eb7f3a6

2 files changed

Lines changed: 124 additions & 3 deletions

File tree

services/core/java/com/android/server/notification/SnoozeHelper.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,29 @@ void cleanupPersistedContext(String key){
142142

143143
protected boolean canSnooze(int numberToSnooze) {
144144
synchronized (mLock) {
145-
if ((mPackages.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) {
145+
if ((mPackages.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT
146+
|| (countPersistedNotificationsLocked() + numberToSnooze)
147+
> CONCURRENT_SNOOZE_LIMIT) {
146148
return false;
147149
}
148150
}
149151
return true;
150152
}
151153

154+
private int countPersistedNotificationsLocked() {
155+
int numNotifications = 0;
156+
for (ArrayMap<String, String> persistedWithContext :
157+
mPersistedSnoozedNotificationsWithContext.values()) {
158+
numNotifications += persistedWithContext.size();
159+
}
160+
for (ArrayMap<String, Long> persistedWithDuration :
161+
mPersistedSnoozedNotifications.values()) {
162+
numNotifications += persistedWithDuration.size();
163+
}
164+
return numNotifications;
165+
}
166+
167+
152168
@NonNull
153169
protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) {
154170
Long time = null;
@@ -451,6 +467,11 @@ protected void repostGroupSummary(String pkg, int userId, String groupKey) {
451467
mPackages.remove(groupSummaryKey);
452468
mUsers.remove(groupSummaryKey);
453469

470+
final String trimmedKey = getTrimmedString(groupSummaryKey);
471+
removeRecordLocked(pkg, trimmedKey, userId, mPersistedSnoozedNotifications);
472+
removeRecordLocked(pkg, trimmedKey, userId,
473+
mPersistedSnoozedNotificationsWithContext);
474+
454475
if (record != null && !record.isCanceled) {
455476
Runnable runnable = () -> {
456477
MetricsLogger.action(record.getLogMaker()

services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
1919
import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
2020

21+
import static com.google.common.truth.Truth.assertThat;
22+
2123
import static junit.framework.Assert.assertEquals;
2224
import static junit.framework.Assert.assertFalse;
2325
import static junit.framework.Assert.assertNull;
@@ -75,6 +77,16 @@
7577
public class SnoozeHelperTest extends UiServiceTestCase {
7678
private static final String TEST_CHANNEL_ID = "test_channel_id";
7779

80+
private static final String XML_TAG_NAME = "snoozed-notifications";
81+
private static final String XML_SNOOZED_NOTIFICATION = "notification";
82+
private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
83+
private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
84+
private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
85+
private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id";
86+
private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version";
87+
private static final String XML_SNOOZED_NOTIFICATION_PKG = "pkg";
88+
private static final String XML_SNOOZED_NOTIFICATION_USER_ID = "user-id";
89+
7890
@Mock SnoozeHelper.Callback mCallback;
7991
@Mock AlarmManager mAm;
8092
@Mock ManagedServices.UserProfiles mUserProfiles;
@@ -328,6 +340,56 @@ public void testSnoozeLimit() {
328340
assertFalse(mSnoozeHelper.canSnooze(1));
329341
}
330342

343+
@Test
344+
public void testSnoozeLimit_maximumPersisted() throws XmlPullParserException, IOException {
345+
final long snoozeTimeout = 1234;
346+
final String snoozeContext = "ctx";
347+
// Serialize & deserialize notifications so that only persisted lists are used
348+
TypedXmlSerializer serializer = Xml.newFastSerializer();
349+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
350+
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
351+
serializer.startDocument(null, true);
352+
serializer.startTag(null, XML_TAG_NAME);
353+
// Serialize maximum number of timed + context snoozed notifications, half of each
354+
for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++) {
355+
final boolean timedNotification = i % 2 == 0;
356+
if (timedNotification) {
357+
serializer.startTag(null, XML_SNOOZED_NOTIFICATION);
358+
} else {
359+
serializer.startTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
360+
}
361+
serializer.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, "pkg");
362+
serializer.attributeInt(null, XML_SNOOZED_NOTIFICATION_USER_ID,
363+
UserHandle.USER_SYSTEM);
364+
serializer.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, 1);
365+
serializer.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, "key" + i);
366+
if (timedNotification) {
367+
serializer.attributeLong(null, XML_SNOOZED_NOTIFICATION_TIME, snoozeTimeout);
368+
serializer.endTag(null, XML_SNOOZED_NOTIFICATION);
369+
} else {
370+
serializer.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, snoozeContext);
371+
serializer.endTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
372+
}
373+
}
374+
serializer.endTag(null, XML_TAG_NAME);
375+
serializer.endDocument();
376+
serializer.flush();
377+
378+
TypedXmlPullParser parser = Xml.newFastPullParser();
379+
parser.setInput(new BufferedInputStream(
380+
new ByteArrayInputStream(baos.toByteArray())), "utf-8");
381+
mSnoozeHelper.readXml(parser, 1);
382+
// Verify that we can't snooze any more notifications
383+
// and that the limit is caused by persisted notifications
384+
assertThat(mSnoozeHelper.canSnooze(1)).isFalse();
385+
assertThat(mSnoozeHelper.isSnoozed(UserHandle.USER_SYSTEM, "pkg", "key0")).isFalse();
386+
assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM,
387+
"pkg", "key0")).isEqualTo(snoozeTimeout);
388+
assertThat(
389+
mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
390+
"key1")).isEqualTo(snoozeContext);
391+
}
392+
331393
@Test
332394
public void testCancelByApp() throws Exception {
333395
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
@@ -601,17 +663,52 @@ public void repostGroupSummary_onlyFellowGroupChildren() throws Exception {
601663

602664
@Test
603665
public void repostGroupSummary_repostsSummary() throws Exception {
666+
final int snoozeDuration = 1000;
604667
IntArray profileIds = new IntArray();
605668
profileIds.add(UserHandle.USER_SYSTEM);
606669
when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
607670
NotificationRecord r = getNotificationRecord(
608671
"pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
609672
NotificationRecord r2 = getNotificationRecord(
610673
"pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
611-
mSnoozeHelper.snooze(r, 1000);
612-
mSnoozeHelper.snooze(r2, 1000);
674+
final long snoozeTime = System.currentTimeMillis() + snoozeDuration;
675+
mSnoozeHelper.snooze(r, snoozeDuration);
676+
mSnoozeHelper.snooze(r2, snoozeDuration);
677+
assertEquals(2, mSnoozeHelper.getSnoozed().size());
678+
assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
679+
// Verify that summary notification was added to the persisted list
680+
assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
681+
r.getKey())).isAtLeast(snoozeTime);
682+
683+
mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
684+
685+
verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
686+
verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r2, false);
687+
688+
assertEquals(1, mSnoozeHelper.getSnoozed().size());
689+
assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
690+
// Verify that summary notification was removed from the persisted list
691+
assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
692+
r.getKey())).isEqualTo(0);
693+
}
694+
695+
@Test
696+
public void snoozeWithContext_repostGroupSummary_removesPersisted() throws Exception {
697+
final String snoozeContext = "zzzzz";
698+
IntArray profileIds = new IntArray();
699+
profileIds.add(UserHandle.USER_SYSTEM);
700+
when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
701+
NotificationRecord r = getNotificationRecord(
702+
"pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
703+
NotificationRecord r2 = getNotificationRecord(
704+
"pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
705+
mSnoozeHelper.snooze(r, snoozeContext);
706+
mSnoozeHelper.snooze(r2, snoozeContext);
613707
assertEquals(2, mSnoozeHelper.getSnoozed().size());
614708
assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
709+
// Verify that summary notification was added to the persisted list
710+
assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
711+
"pkg", r.getKey())).isEqualTo(snoozeContext);
615712

616713
mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
617714

@@ -620,6 +717,9 @@ public void repostGroupSummary_repostsSummary() throws Exception {
620717

621718
assertEquals(1, mSnoozeHelper.getSnoozed().size());
622719
assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
720+
// Verify that summary notification was removed from the persisted list
721+
assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
722+
"pkg", r.getKey())).isNull();
623723
}
624724

625725
@Test

0 commit comments

Comments
 (0)