
Exploit de prueba de concepto para CVE-2022-22706: explota una falla de escritura en la caché de páginas del controlador del kernel de GPU Mali para modificar /etc/passwd en memoria y obtener un shell de root.
El controlador de GPU Arm Mali entrega al espacio de usuario un mapeo escribible por CPU de páginas que fijó como solo lectura,
por lo que un proceso sin privilegios obtiene un alias escribible de la caché de páginas que respalda un archivo que solo puede
abrir con O_RDONLY.
exploit.c vacía el campo de contraseña de root en la caché de páginas de /etc/passwd y ejecuta
su root. El archivo en disco nunca se modifica.
PoC para el árbol del controlador y el objetivo QEMU en este repositorio (
mali_kbaser35p0-01eac0,CONFIG_MALI_NO_MALI=y, x86_64 GKI 5.15). Ejecútalo en la VM.
Dos decisiones discrepan sobre quién puede escribir en las páginas importadas.
La capacidad de escritura del mapeo de CPU proviene de KBASE_REG_CPU_WR, pero la fijación solicita acceso de escritura
basándose únicamente en 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 activado y GPU_WR desactivado, y obtienes ambas mitades a la vez: un mapeo de CPU escribible
de la importación, y una fijación de get_user_pages tomada sin FOLL_WRITE. Sin
FOLL_WRITE, get_user_pages nunca rompe el COW en un mapeo de archivo de solo lectura; devuelve la
propia página de la caché de páginas. El controlador entonces mapea esas mismas páginas de vuelta al espacio de usuario como escribibles.
Corregido por 5381ff7
("GPUCORE-32592 Fix userbuf imports to respect RO memory"), que deriva el acceso de escritura de
KBASE_REG_CPU_WR | KBASE_REG_GPU_WR en lugar de 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 solo registra una dirección; la fijación ocurre más tarde, en JOB_SUBMIT. Ese intervalo es
lo que permite que la página anónima se intercambie por el mapeo del archivo en el medio.
La edición preserva la longitud, por lo que nada después de la línea de root se desplaza:
root:x:0:0:root:/root:/bin/sh ← before
root::0:0:rootx:/root:/bin/sh ← after (empty password)
El su de busybox devuelve CHECKPASS_PW_HAS_EMPTY_PASSWORD antes de solicitar la contraseña cuando el campo
de contraseña está vacío, y solo lee /etc/shadow cuando es exactamente x.
gcc -static -o exploit exploit.c
Cópialo en la VM y ejecútalo como un usuario sin privilegios:
$ ./exploit

Grabado por SSH en el objetivo QEMU como user (uid 1000). ./exploit parchea la línea de root
en la caché de páginas de /etc/passwd y ejecuta su root, que cae directamente a un shell de root
sin pedir contraseña.
Al salir de ese shell y ejecutar su de nuevo, todavía se obtiene root: la página permanece en caché, por lo que cada
open()/read() posterior de /etc/passwd ve los bytes parcheados.
Después de que echo 1 > /proc/sys/vm/drop_caches expulsa la página y el archivo se vuelve a leer del almacenamiento,
su vuelve a pedir una contraseña. Los bytes en disco nunca se tocaron.