CVE-2021-39793 / CVE-2022-22706: Mali GPU driver makes read-only imported pages host-writable (in-the-wild)¶
Mali GPU'ya import edilen bir host buffer pin'lenirken
kbase_jd_user_buf_pin_pages()yalnızca GPU-write flag'ini kontrol edip CPU-write flag'ini görmezden gelir, böylece CPU read-only page'ler write erişimiyle pin'lenir — read-only memory'yi (page table'lar dahil) attacker tarafından yazılabilir hale getiren, in-the-wild görülmüş bir Android primitive.
Mechanism¶
Neden çalışır: pin'lenen import page'lerin write-permission kararında eksik tek bir flag
ARM Mali GPU kernel driver'ı (kbase), userspace'in kendi host virtual
memory'sinin bir bölgesini KBASE_MEM_TYPE_IMPORTED_USER_BUF tipinde bir
memory object olarak GPU'ya import etmesine izin verir. Bu host page'lerin
GPU tarafından kullanılabilmesi için driver onları pin'lemek zorunda —
uzun ömürlü reference'lar alıp onları physical page'lere çözmek — bunu da
pin_user_pages_remote() ile yapar.
pin_user_pages_remote(), pin'in page'lere write erişimi alıp
almayacağını söyleyen bir flag alır: write isteniyorsa FOLL_WRITE,
read-only ise 0. CPU read-only olan bir page üzerinde write erişimi
istemek, kernel'in GUP (get-user-pages) mekanizmasının page'i yazılabilir
yapmasına neden olur — copy-on-write'ı break eder ve yazılabilir bir
physical mapping geri verir. Yani write/no-write kararı security açısından
kritiktir: read-only memory'nin read-only kalıp kalmayacağına o karar verir.
Bir Mali region iki bağımsız permission flag taşır:
KBASE_REG_GPU_WR— GPU bu page'lere yazabilir mi (GPU page table'ları)?KBASE_REG_CPU_WR— CPU/host bu page'lere yazabilir mi?
Bug şu: zafiyetli driver'larda kbase_jd_user_buf_pin_pages() write
isteğini yalnızca KBASE_REG_GPU_WR'den hesaplar, KBASE_REG_CPU_WR'ı
görmezden gelir:
/* vulnerable: only consults the GPU-write flag */
write = reg->flags & KBASE_REG_GPU_WR;
pin_user_pages_remote(..., write ? FOLL_WRITE : 0, ...);
Amaç, CPU read-only / GPU read-only bir import'un read-only pin'lenmesiydi. Ama karar yanlış flag'i kullandığı için, attacker'ın CPU read-only olarak işaretlediği bir region, GPU-writable olduğu sürece yine de writable pin'lenebiliyor (ya da tersine, kontrol CPU-write niyetine saygı göstermiyor). Sonuç: GUP, alttaki CPU read-only page'leri host-writable yapıyor. Immutable olması gereken memory'yi — en güçlü haliyle page-table page'leri veya diğer read-only kernel/userspace mapping'leri — import ederek, attacker access control'lerin read-only dediği memory'ye bir write elde ediyor.
Doğru fix iki flag'i birleştirir:
/* fixed: write if EITHER CPU or GPU write is permitted */
write = reg->flags & (KBASE_REG_CPU_WR | KBASE_REG_GPU_WR);
pin_user_pages_remote(..., write ? FOLL_WRITE : 0, ...);
Bu, daha önceki kbase_mem_from_user_buffer() fix'iyle (CVE-2021-28664)
aynı sınıftan bir hata: çoğaltılmış bir import code path'i patch'lenmemiş,
paralel bir zafiyetli kopya bırakmış. ARM bunu Mali driver r36p0'da
(2022-02-11'de yayınlandı) düzeltti ve Android Security Bulletin'de
exploited-in-the-wild bir bug olarak yer aldı. Aynı kusur iki identifier
taşıyor — CVE-2021-39793 (Android/Pixel tracking) ve CVE-2022-22706
(ARM tracking). Açıklaması, Project Zero'dan Jann Horn'u Mali'yi audit edip
beş sorun daha bulmaya yönelten o in-the-wild Pixel 6 bug'ıydı.
Walkthrough¶
Primitive, Mali kbase device node'u üzerinden unprivileged app sandbox'ından
erişilebilir. Project Zero RCA'sını izleyen kavramsal akış:
CPU read-only bir buffer'ı import edip writable pin'letmek
int fd = open("/dev/mali0", O_RDWR);
/* 1. Obtain a CPU read-only region whose pages we want to corrupt — e.g.
a read-only file mapping, or memory that should be immutable. */
void *ro = mmap(NULL, len, PROT_READ, MAP_PRIVATE, target_fd, 0);
/* 2. Import it into Mali as KBASE_MEM_TYPE_IMPORTED_USER_BUF, with region
flags that keep CPU access read-only (KBASE_REG_CPU_WR clear) but mark
it GPU-writable (KBASE_REG_GPU_WR set). */
union kbase_ioctl_mem_import imp = {
.in = { .type = BASE_MEM_IMPORT_TYPE_USER_BUFFER,
.phandle = (uintptr_t)&user_buf, /* ptr+len of `ro` */
.flags = BASE_MEM_PROT_GPU_WR /* GPU write, no CPU write */ },
};
ioctl(fd, KBASE_IOCTL_MEM_IMPORT, &imp);
Pin'i tetiklemek — import edilmiş user buffer'ı pin'leyen herhangi bir GPU
operasyonu, page'ler CPU read-only olsa bile kbase_jd_user_buf_pin_pages()'in
pin_user_pages_remote()'u FOLL_WRITE ile çağırmasına neden olur:
/* Submit a GPU job (or otherwise map the imported handle for GPU use) that
forces the driver to pin the imported user buffer. */
struct kbase_ioctl_job_submit js = { /* atom referencing imp handle */ };
ioctl(fd, KBASE_IOCTL_JOB_SUBMIT, &js);
/* Kernel: write = reg->flags & KBASE_REG_GPU_WR; -> nonzero
pin_user_pages_remote(..., FOLL_WRITE, ...);
GUP breaks COW / makes the CPU read-only pages host-writable. */
Zafiyetli bir build'de beklenen sonuç: import edilmiş, sözde read-only page'ler host tarafından yazılabilir hale gelir. Read-only page-table page'lerini import edip sonra onlara yazmak ders kitabı escalation'ıdır — bir PTE'yi attacker data'ya işaret edecek şekilde flip et, arbitrary kernel read/write elde edersin; bkz. dirty-pagetable-page-table-data-only-attack.md.
r36p0 veya sonrası bir driver'da write kararı KBASE_REG_CPU_WR'ı OR'lar,
böylece CPU read-only bir import read-only pin'lenir (FOLL_WRITE set
edilmez) ve page'ler immutable kalır.
Detection¶
Göstergeler
/dev/mali0açan, read-only mapping'lerle desteklenen user buffer'ları (KBASE_IOCTL_MEM_IMPORT, user-buffer type) import edip ardından onları pin'leyen job'lar submit eden unprivileged app'ler.- Read-only file mapping'leriyle veya page-table benzeri memory ile çakışan region'ların import edilmesi.
- Cihazların 2022-03 (veya sonrası) Android patch level'ını / Mali r36p0+ taşıdığını doğrula.
Mitigation¶
Fix ve hardening
- ARM Mali driver'ı r36p0 veya sonrasına güncelle (Android 2022-03
security patch level). Fix, pin write-kararının hem
KBASE_REG_CPU_WRhem deKBASE_REG_GPU_WR'a saygı göstermesini sağlar. /dev/mali0erişimini SELinux ile kısıtla, böylece yalnızca graphics stack import/pin path'lerine erişebilsin.- Çoğaltılmış import code path'lerini aynı flag-check pattern'i için audit
et — bu bug, daha önceki
kbase_mem_from_user_buffer()fix'i (CVE-2021-28664) duplikatı atladığı için vardı.