
Exploit para CVE-2026-46215, uma escalada de privilégio local use-after-free no DRM GEM do kernel Linux. Usa corrida, slab spraying e sobrescrita de arquivo estilo Dirty Pipe para transformar um usuário não privilegiado do nó de render em root sem senha.
change_handle Use-After-Free (LPE sem privilégio)Escalação de privilégio local via um use-after-free no ioctl do núcleo do DRM
DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, ioctl nr 0xD2).
Acessível por qualquer usuário com acesso a um render node (/dev/dri/renderD*,
concedido à sessão ativa pelo systemd-logind em todas as principais distribuições
desktop). A cadeia neste repositório leva o UAF a root sem senha a partir de
um usuário não privilegiado.
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)53096728b891 ("drm: Add DRM prime interface to reassign GEM handle", David Francis / AMD), adicionado para o trabalho de CRIU da AMD5e28b7b94408). O ioctl também está sendo desabilitado no upstream na versão 7.1 devido a esta e outras condições de corrida.Este bug foi relatado pela primeira vez por Puttimet Thammasaeng, que detém o crédito
Reported-by do upstream na correção. Eu o encontrei e reportei de forma independente para
[email protected] em 2026-04-12. Meu relatório foi reconhecido e encaminhado para
os mantenedores, mas o relatório anterior é o creditado no upstream. Este repositório
contém minha própria análise e exploit.
Writeup: https://cyberstan.co.uk
A correção está em kernels estáveis lançados (6.18.32 e 7.0.9 em diante). Este repositório foi publicado depois que as correções se tornaram amplamente disponíveis.
drm_gem_change_handle_ioctl() move um objeto GEM de um handle para outro
mas nunca ajusta obj->handle_count. Também pula drm_vma_node_allow/revoke
e os callbacks de abertura/fechamento do driver. Como handle_count permanece em 1, um
GEM_CLOSE concorrente no handle antigo o reduz para 0 e libera o objeto
enquanto o novo handle ainda o referencia no IDR. Esse handle pendente é o
use-after-free, posteriormente desreferenciado em drm_gem_object_release_handle().
GEM_CHANGE_HANDLE contra GEM_CLOSE para obter um handle pendente.pipe_buffer pulverizado
(feng shui de msg_msg para condicionar kmalloc-512, depois pipes preenchidos por splice).pipe_buf_ops através de um ioctl de informações do driver: obj->size (offset 216)
se sobrepõe a pipe_buf[5].ops, dando um ponteiro do kernel e a base do KASLR.obj->name (offset 224) caia sobre
pipe_buf[5].flags e defina PIPE_BUF_FLAG_CAN_MERGE (name = 16 = 0x10)./etc/passwd, root se torna sem senha.Os offsets são verificados via pahole e são específicos da disposição entre 6.18 e
7.0. Sobrescreva as definições GEM_* / PIPEBUF_* para outros kernels.
poc.c - o exploit. Compile estático com -lpthread.run_exploit.sh - compila o PoC e um initramfs mínimo, inicializa-o no QEMU.qemu-system-x86_64, gcc, busybox (estático), fakeroot, cpio, gzip.
KVM (/dev/kvm) é recomendado; a condição de corrida é muito mais confiável com ele.
O exploit precisa de um alvo vulnerável compilado com opções específicas:
CONFIG_KASAN deve estar DESLIGADO. KASAN coloca em quarentena slabs liberados e bloqueia a
recuperação por spray de pipe, então o exploit não funcionará contra uma compilação com KASAN.virtio_gpu (usado pela demonstração) ou nouveau.CONFIG_DRM=y, CONFIG_DRM_VIRTIO_GPU=y, CONFIG_DEVTMPFS=y,
CONFIG_BLK_DEV_INITRD=y.Passos:
CONFIG_KASAN não está definido.make -j"$(nproc)" bzImage, produzindo arch/x86/boot/bzImage.A demonstração inicializa com nokaslr para que o ponteiro vazado seja determinístico. O vazamento
quebra o KASLR por si só, então KASLR ligado também funciona, o endereço apenas varia a cada
inicialização.
Veja drivers/gpu/drm/drm_gem.c, função drm_gem_change_handle_ioctl().
Verificação rápida:
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 significa VULNERÁVEL (sem tratamento de refcount).Por estrutura:
file_priv->prime.lock, um único
idr_alloc(&file_priv->object_idr, obj, ...), depois idr_remove() no handle
antigo. Nenhum drm_gem_object_handle_get.idr_alloc depois idr_replace(NULL) para desassociar o
handle antigo), com o objeto real sendo trocado apenas quando as operações de prime
são bem-sucedidas../run_exploit.sh /caminho/para/bzImage
Ele compila poc.c, empacota em um initramfs e inicializa QEMU com
-device virtio-gpu-pci e nokaslr. O PoC executa como uid 1000 (não privilegiado),
então o script imprime /etc/passwd antes e depois e te joga em um shell.
Saia da VM com poweroff -f ou Ctrl-A X.
[!] Corrida vencida (iter 977): handle=132049
[!] KASLR: pipe_buf_ops = 0xffffffff82428400
[!] EXPLOIT BEM-SUCEDIDO
[!] FLINK: 16 = 0x10
[*] /etc/passwd:
root::0:0:pwned:/root:/bin/sh
[!] LPE CONFIRMADO
[!] conta root agora está sem senha
Um /etc/passwd somente leitura (chmod 444) de propriedade do root é sobrescrito por um
processo não privilegiado. A linha root: perde o campo de senha.
Recompile contra uma árvore corrigida (6.18.32, 7.0.9, 7.1-rc3 ou posterior, ou qualquer árvore que passe na verificação de corrigido acima) e execute novamente:
./run_exploit.sh /caminho/para/patched-bzImage
Contra o kernel corrigido, o exploit nunca deve chegar a "EXPLOIT BEM-SUCEDIDO": a corrida não libera mais o objeto por baixo do novo handle, então o vazamento nunca retorna um ponteiro de kernel válido.
Publicado para fins de pesquisa e defesa após as correções do upstream serem lançadas. Fornecido como está. Execute apenas em VMs que você controla.