
Exploit de prova de conceito para CVE-2022-22706: explora uma falha de gravação no cache de páginas do driver do kernel da GPU Mali para modificar o /etc/passwd na memória e obter um shell root.
O driver de GPU Arm Mali entrega ao userspace um mapeamento gravável pela CPU das páginas que fixou como somente leitura; portanto, um processo não privilegiado obtém um alias gravável do cache de páginas que dá suporte a um arquivo que ele só pode abrir com O_RDONLY.
exploit.c limpa o campo de senha de root no cache de páginas do /etc/passwd e executa su root. O arquivo em disco nunca é modificado.
PoC para a árvore do driver e o alvo QEMU neste repositório (
mali_kbaser35p0-01eac0,CONFIG_MALI_NO_MALI=y, x86_64 GKI 5.15). Execute-o na VM.
Duas decisões discordam quanto a quem pode escrever nas páginas importadas.
A permissão de escrita do mapeamento da CPU vem de KBASE_REG_CPU_WR, mas a fixação das páginas pede acesso de escrita com base apenas em 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);
Importe com CPU_WR definido e GPU_WR limpo e você obtém as duas metades ao mesmo tempo: um mapeamento de CPU gravável da importação e uma fixação de get_user_pages feita sem FOLL_WRITE. Sem FOLL_WRITE, o get_user_pages nunca quebra o COW em um mapeamento de arquivo somente leitura; ele retorna a própria página do cache de páginas. O driver então mapeia exatamente essas páginas de volta para o userspace como graváveis.
Corrigido por 5381ff7
("GPUCORE-32592 Fix userbuf imports to respect RO memory"), que deriva o acesso de escrita de KBASE_REG_CPU_WR | KBASE_REG_GPU_WR em vez de apenas 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 apenas registra um endereço; a fixação acontece depois, em JOB_SUBMIT. Essa lacuna é o que permite que a página anônima seja trocada pelo mapeamento do arquivo nesse intervalo.
A edição preserva o comprimento, portanto nada depois da linha de root é deslocado:
root:x:0:0:root:/root:/bin/sh ← before
root::0:0:rootx:/root:/bin/sh ← after (empty password)
O su do busybox retorna CHECKPASS_PW_HAS_EMPTY_PASSWORD antes de pedir a senha quando o campo de senha está vazio, e só lê /etc/shadow quando o campo é exatamente x.
gcc -static -o exploit exploit.c
Copie-o para a VM e execute-o como um usuário não privilegiado:
$ ./exploit

Gravado via SSH para o alvo QEMU como user (uid 1000). ./exploit altera a linha de root no cache de páginas do /etc/passwd e executa su root, que cai direto em um shell root sem pedir senha.
Sair desse shell e executar su novamente ainda dá root: a página permanece no cache, então cada open()/read() posterior de /etc/passwd enxerga os bytes alterados.
Depois que echo 1 > /proc/sys/vm/drop_caches expulsa a página do cache e o arquivo é relido do armazenamento, su pede a senha novamente. Os bytes em disco nunca foram tocados.