Анализ уязвимости повышения привилегий CVE-2020-0108 в службах переднего плана
1. Контекст уязвимости
- В патче AOSP за август 2020 года была раскрыта уязвимость уровня фреймворка в AMS, получившая номер CVE-2020-0108 и рейтинг High. Это логическая уязвимость в обработке служб переднего плана в AMS: злоумышленник, успешно использующий её, может обойти отображение уведомления службы переднего плана и продолжать работу в фоновом режиме. Атака инициируется локальным вредоносным приложением и не требует взаимодействия с пользователем. Если пользователь предоставил приложению другие разрешения, это может привести к более серьёзному ущербу, например, к непрерывному отслеживанию местоположения или скрытой записи звука.
2. Детали уязвимости
- Служба переднего плана (foreground service) — это концепция, введённая Google в Android 8.0. Поскольку Android 8.0 не позволяет запускать фоновые службы в фоне, была разработана концепция службы переднего плана: она имеет более высокий приоритет и может работать в фоновом режиме длительное время, однако в течение 5 секунд после запуска служба переднего плана должна привязать уведомление, иначе она будет остановлена. Фактически служба переднего плана по-прежнему работает в «фоне», но поскольку к ней привязано видимое пользователю уведомление, Google называет её «службой переднего плана».
- Данная уязвимость имеет два метода атаки, соответствующих двум логическим ошибкам.
- Первая ошибка находится в методе 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 передаёт несуществующий идентификатор ресурса resID, то при разборе макета уведомления в 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);
}
}
});
}
}
- В этом случае после запуска службы переднего плана, если пользовательское приложение перехватывает исключения главного потока, служба переднего плана не будет остановлена, даже если уведомление не было корректно отображено. Например, если при создании уведомления служба переднего плана передаёт недопустимый идентификатор канала Channel ID, то при отправке уведомления в методе postNotification класса ServiceRecord будет выброшено исключение. В процессе обработки исключения вызывается лишь метод crashApplication из AMS, который выбрасывает исключение в главном потоке приложения, но если приложение перехватывает это исключение в главном потоке, оно не аварийно завершается, и служба переднего плана продолжает работать без отображения уведомления.