
Exploit proof-of-concept per CVE-2022-22706: sfrutta una vulnerabilità di scrittura nella page-cache del driver del kernel GPU Mali per modificare /etc/passwd in memoria e ottenere una shell di root.
Il driver GPU Arm Mali fornisce allo spazio utente una mappatura scrivibile dalla CPU di pagine che ha bloccato in sola lettura, quindi un processo non privilegiato ottiene un alias scrivibile della page cache di un file che può aprire solo con O_RDONLY.
exploit.c azzera il campo della password di root nella page cache di /etc/passwd ed esegue su root. Il file su disco non viene mai modificato.
PoC per l'albero del driver e il target QEMU in questo repo (
mali_kbaser35p0-01eac0,CONFIG_MALI_NO_MALI=y, x86_64 GKI 5.15). Eseguilo nella VM.
Due decisioni sono in disaccordo su chi possa scrivere le pagine importate.
La scrivibilità della mappatura CPU deriva da KBASE_REG_CPU_WR, ma il pin richiede l'accesso in scrittura basandosi solo su KBASE_REG_GPU_WR:
/* mali_kbase_mem.c */
pinned_pages = pin_user_pages_remote(
mm, address, alloc->imported.user_buf.nr_pages,
reg->flags & KBASE_REG_GPU_WR ? FOLL_WRITE : 0, pages, NULL, NULL);
Importa con CPU_WR impostato e GPU_WR non impostato e ottieni entrambe le metà in una volta: una mappatura CPU scrivibile dell'import e un pin get_user_pages eseguito senza FOLL_WRITE. Senza FOLL_WRITE, get_user_pages non interrompe mai il COW su una mappatura di file in sola lettura; restituisce la pagina stessa della page cache. Il driver mappa quindi quelle stesse pagine di nuovo nello spazio utente come scrivibili.
Corretto da 5381ff7
("GPUCORE-32592 Fix userbuf imports to respect RO memory"), che deriva l'accesso in scrittura da KBASE_REG_CPU_WR | KBASE_REG_GPU_WR invece che dal solo GPU_WR.
sequenceDiagram
participant U as unprivileged process
participant K as mali_kbase
participant PC as page cache
U->>U: mmap /etc/passwd O_RDONLY, PROT_READ
U->>K: MEM_IMPORT(anon page, CPU_RD|CPU_WR|GPU_RD)
Note over K: address recorded, nothing pinned yet
U->>U: munmap(anon) + mremap file mapping onto that VA
U->>K: mmap(import cookie) → writable CPU mapping
U->>K: JOB_SUBMIT(EXTERNAL_RESOURCES)
K->>PC: pin_user_pages_remote() without FOLL_WRITE
U->>PC: memcpy() through the writable mapping
U->>U: execl("/bin/su", "su", "root")
MEM_IMPORT registra solo un indirizzo; il pin avviene dopo, in JOB_SUBMIT. È questo intervallo che consente di sostituire la pagina anonima con la mappatura del file nel frattempo.
La modifica preserva la lunghezza, quindi nulla dopo la riga di root si sposta:
root:x:0:0:root:/root:/bin/sh ← before
root::0:0:rootx:/root:/bin/sh ← after (empty password)
busybox su restituisce CHECKPASS_PW_HAS_EMPTY_PASSWORD prima di chiedere la password quando il campo della password è vuoto, e legge /etc/shadow solo quando è esattamente x.
gcc -static -o exploit exploit.c
Copialo nella VM ed eseguilo come utente non privilegiato:
$ ./exploit

Registrato via SSH nel target QEMU come user (uid 1000). ./exploit modifica la riga di root nella page cache di /etc/passwd ed esegue su root, che porta direttamente a una shell di root senza alcuna richiesta.
Uscendo da quella shell ed eseguendo di nuovo su si ottiene ancora root: la pagina rimane in cache, quindi ogni successiva open()/read() di /etc/passwd vede i byte modificati.
Dopo che echo 1 > /proc/sys/vm/drop_caches espelle la pagina e il file viene riletto dallo storage, su richiede di nuovo la password. I byte su disco non sono mai stati toccati.