
Exploit de preuve de concept pour CVE-2022-22706 : exploite un défaut d'écriture dans le cache de pages du pilote du noyau GPU Mali pour modifier /etc/passwd en mémoire et obtenir un shell root.
Le pilote GPU Arm Mali fournit à l'espace utilisateur un mapping CPU accessible en écriture de pages qu'il a épinglées en lecture seule,
de sorte qu'un processus non privilégié obtient un alias accessible en écriture du cache de pages adossant un fichier qu'il ne peut
ouvrir qu'en O_RDONLY.
exploit.c vide le champ mot de passe de root dans le cache de pages de /etc/passwd puis exécute
su root. Le fichier sur disque n'est jamais modifié.
PoC pour l'arborescence du pilote et la cible QEMU de ce dépôt (
mali_kbaser35p0-01eac0,CONFIG_MALI_NO_MALI=y, x86_64 GKI 5.15). Exécutez-le dans la VM.
Deux décisions divergent quant à savoir qui peut écrire dans les pages importées.
L'accessibilité en écriture du mapping CPU provient de KBASE_REG_CPU_WR, mais l'épingle demande un accès en écriture
fondé uniquement sur 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);
Importez avec CPU_WR positionné et GPU_WR non positionné et vous obtenez les deux moitiés à la fois : un mapping CPU
accessible en écriture de l'import, et un épinglage get_user_pages réalisé sans FOLL_WRITE. Sans
FOLL_WRITE, get_user_pages ne casse jamais le COW sur un mapping de fichier en lecture seule ; il retourne la
page du cache de pages elle-même. Le pilote mappe ensuite ces pages exactes vers l'espace utilisateur en écriture.
Corrigé par 5381ff7
(« GPUCORE-32592 Fix userbuf imports to respect RO memory »), qui dérive l'accès en écriture de
KBASE_REG_CPU_WR | KBASE_REG_GPU_WR plutôt que de GPU_WR seul.
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 ne fait qu'enregistrer une adresse ; l'épinglage a lieu plus tard, au JOB_SUBMIT. C'est cet écart
qui permet de remplacer la page anonyme par le mapping du fichier entre-temps.
La modification préserve la longueur, donc rien ne se décale après la ligne de root :
root:x:0:0:root:/root:/bin/sh ← before
root::0:0:rootx:/root:/bin/sh ← after (empty password)
busybox su renvoie CHECKPASS_PW_HAS_EMPTY_PASSWORD avant d'afficher une invite lorsque le champ mot de passe
est vide, et ne lit /etc/shadow que lorsqu'il contient exactement x.
gcc -static -o exploit exploit.c
Copiez-le dans la VM et exécutez-le en tant qu'utilisateur non privilégié :
$ ./exploit

Enregistré via SSH dans la cible QEMU en tant que user (uid 1000). ./exploit modifie la ligne de root
dans le cache de pages de /etc/passwd et exécute su root, qui donne directement un shell root
sans invite.
En quittant ce shell et en relançant su, on obtient encore root : la page reste en cache, donc chaque
open()/read() ultérieur de /etc/passwd voit les octets modifiés.
Après que echo 1 > /proc/sys/vm/drop_caches a expulsé la page et que le fichier est relu depuis le stockage,
su redemande un mot de passe. Les octets sur disque n'ont jamais été touchés.