flawopen.com/Teardowns/cve-2024-23712-android-appops-attribution-tag-dos
CVE-2024-23712: Kehabisan Sumber Daya lewat Tag Atribusi di AppOps Android
CVE-2024-23712 (CVSS 5.5; Android menilainya tinggi): AppOpsService mencatat tag atribusi proxy apa pun yang diberikan aplikasi pemanggil, sehingga aplikasi biasa tanpa izin tambahan dapat membuat /data/system/appops_accesses.xml membengkak tanpa batas dan memicu penolakan layanan lokal.
Resepsionis sebuah gedung kantor menyimpan buku tamu dengan satu halaman per departemen, dan setiap kurir mengisi kolom "didampingi departemen". Tidak ada yang mencocokkan kolom itu dengan daftar resmi departemen. Seorang kurir menulis nama departemen karangan yang baru di setiap pengiriman, ribuan kali sehari. Buku itu menambah halaman untuk setiap nama dan disalin utuh ke arsip setiap malam, sampai resepsionis menghabiskan seluruh waktunya menyalin halaman. Perbaikannya: kosongkan kolom itu bila nama tidak ada di daftar resmi, dan batasi berapa banyak departemen yang boleh didaftarkan satu perusahaan.
Konsep Kunci & Istilah
AppOpsService- Layanan di
system_serveryang mencatat aplikasi mana memakai operasi terlindungi mana (kamera, lokasi, mikrofon, dan sebagainya), kapan, dan atas nama siapa. Tag atribusi- Label yang dideklarasikan aplikasi di manifes dengan
<attribution android:tag=...>untuk mengaitkan akses data ke fitur tertentu. Platform membatasi jumlah yang boleh dideklarasikan satu paket (MAX_NUM_ATTRIBUTIONS). Operasi proxy- App-op yang dicatat atas nama aplikasi lain, seperti dalam rantai
AttributionSource. Uid, paket, dan tag atribusi proxy disimpan bersama catatan akses. appops_accesses.xml- File di
/data/systemtempat AppOpsService menyimpan catatan akses; ditulis ulang olehscheduleWriteLocked()dan diurai lagi saat booting.
Analisis Akar Masalah (Root Cause)
noteOperationUnchecked() dan startOperationUnchecked() di AppOpsService.java meneruskan proxyAttributionTag yang dikendalikan pemanggil langsung ke AttributedOp tanpa memeriksa apakah aplikasi proxy mendeklarasikan tag itu. Setiap string berbeda menghasilkan satu catatan proxy lagi yang tetap di memori dan ditulis ke .accessed()/data/system/appops_accesses.xml, sehingga satu aplikasi saja dapat menggembungkan file itu sampai system_server kehabisan memori dan I/O. Perbaikannya mengosongkan (null) setiap tag yang tidak ditemukan isAttributionTagDefined() di manifes kedua paket, dan menurunkan MAX_NUM_ATTRIBUTIONS dari 10.000 menjadi 1.000.
Alur Serangan Langkah demi Langkah
Aplikasi biasa bertindak sebagai proxy
Aplikasi terpasang tanpa izin khusus mencatat app-op atas nama paket lain dan meneruskan tag atribusi proxy miliknya lewat AppOpsManager.
Tag baru di setiap panggilan
Aplikasi membuat string tag baru yang tidak dideklarasikan di setiap panggilan. noteOperationUnchecked() menerima semuanya apa adanya.
Status tumbuh tanpa batas
AttributedOp menyimpan satu catatan untuk setiap tag proxy yang berbeda, dan .accessed()scheduleWriteLocked() terus menulis kumpulan yang makin besar ke appops_accesses.xml.
Penolakan layanan lokal
system_server menghabiskan makin banyak memori dan I/O disk untuk menserialisasi dan mengurai ulang file itu, sehingga perangkat melambat atau macet, bahkan setelah reboot.
Kode Sumber: Rentan vs Aman
// services/core/java/com/android/server/appop/AppOpsService.java (before 2024-04-01)
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int proxyUid, String proxyPackageName,
@Nullable String proxyAttributionTag, @OpFlags int flags,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
// (uid and package verification elided)
// BUG: proxyAttributionTag comes from the calling app and is never checked
// against the <attribution> tags that app declared. Each new string becomes
// another attributed entry, held in memory and persisted to
// /data/system/appops_accesses.xml.
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
uidState.getState(), flags);
scheduleWriteLocked();
}
return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, packageName);
}
// services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
/** Maximum amount of attributions per package */
static final int MAX_NUM_ATTRIBUTIONS = 10000;
// services/core/java/com/android/server/appop/AppOpsService.java (2024-04-01, AOSP 6beb68ca)
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int proxyUid, String proxyPackageName,
@Nullable String proxyAttributionTag, @OpFlags int flags,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
// (uid and package verification elided)
// FIX: drop a proxy attribution tag that neither package declared, so a
// caller can no longer mint unlimited entries. startOperationUnchecked()
// gets the same check.
if (proxyAttributionTag != null
&& !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
proxyAttributionTag = null;
}
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
uidState.getState(), flags);
scheduleWriteLocked();
}
return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, packageName);
}
private boolean isAttributionTagDefined(@Nullable String packageName,
@Nullable String proxyPackageName, @Nullable String attributionTag) {
if (packageName == null) {
return false;
} else if (attributionTag == null) {
return true;
}
PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
if (proxyPackageName != null) {
AndroidPackage proxyPkg = pmInt.getPackage(proxyPackageName);
if (proxyPkg != null && isAttributionInPackage(proxyPkg, attributionTag)) {
return true;
}
}
AndroidPackage pkg = pmInt.getPackage(packageName);
return isAttributionInPackage(pkg, attributionTag);
}
// services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
/** Maximum amount of attributions per package */
static final int MAX_NUM_ATTRIBUTIONS = 1000;
Daftar Periksa Penguatan Sistem Rekayasa
- ✓Kirim perangkat dengan tingkat patch keamanan 2024-04-01 atau lebih baru, yang memuat commit AOSP 6beb68ca.
- ✓Di layanan sistem mana pun, cocokkan pengenal yang dikirim klien dengan registri di sisi server (di sini manifes paket) sebelum memakainya sebagai kunci status tersimpan.
- ✓Terapkan batas keras jumlah entri per klien pada setiap struktur yang disimpan; patch ini menurunkan
MAX_NUM_ATTRIBUTIONSdari 10.000 menjadi 1.000. - ✓Tambahkan tes yang mengirim ribuan kunci unik pilihan klien dan memastikan status tersimpan serta file yang disimpan tetap di bawah ukuran tetap.
- ✓Di aplikasi, deklarasikan setiap tag atribusi di manifes dan buat konteks dengan
Contexthanya untuk tag yang dideklarasikan; tag proxy yang tidak dideklarasikan kini dibuang..createAttributionContext()