Skip to content

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_WRGPU bu page'lere yazabilir mi (GPU page table'ları)?
  • KBASE_REG_CPU_WRCPU/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/mali0 aç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_WR hem de KBASE_REG_GPU_WR'a saygı göstermesini sağlar.
  • /dev/mali0 eriş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ı.

References