AF_UNIX MSG_OOB UAF & SKB-based kernel primitives
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
TL;DR
- Linux >=6.9 introduziu um refactor defeituoso em
manage_oob()(5aa57d9f2d53) para o tratamento de AF_UNIXMSG_OOB. SKBs empilhados com comprimento zero contornaram a lógica que limpau->oob_skb, de modo que umrecv()normal podia liberar o SKB out-of-band enquanto o ponteiro permanecia vivo, resultando em CVE-2025-38236. - Re-disparar
recv(..., MSG_OOB)desreferencia ostruct sk_buffpendente. ComMSG_PEEK, o caminhounix_stream_recv_urg() -> __skb_datagram_iter() -> copy_to_user()torna-se uma leitura arbitrária de kernel estável de 1-byte; semMSG_PEEKo primitivo incrementaUNIXCB(oob_skb).consumedno offset0x44, isto é, adiciona +4 GiB ao dword superior de qualquer valor 64-bit colocado no offset0x40dentro do objeto realocado. - Drenando páginas não-movíveis de ordem 0/1 (page-table spray), forçando a liberação de uma página de slab de SKB para o buddy allocator, e reutilizando a página física como pipe buffer, o exploit forja metadados de SKB em memória controlada para identificar a página pendente e pivotar o primitivo de leitura para
.data, vmemmap, per-CPU e regiões de page-table apesar do hardening de usercopy. - A mesma página pode depois ser reciclada como a página superior da kernel-stack de uma thread recém-clonada.
CONFIG_RANDOMIZE_KSTACK_OFFSETtorna-se um oráculo: ao sondar o layout da stack enquantopipe_write()bloqueia, o atacante espera até que o comprimento derramado decopy_page_from_iter()(R14) caia no offset0x40, e então dispara o incremento de +4 GiB para corromper o valor na stack. - Uma
skb_shinfo()->frag_listem loop mantém a syscall UAF girando no espaço do kernel até que uma thread cooperante travecopy_from_iter()(viamprotect()sobre um VMA contendo um único buracoMADV_DONTNEED). Quebrar o loop libera o incremento exatamente quando o alvo na stack está vivo, inflando o argumentobytesde modo quecopy_page_from_iter()escreva além da página do pipe buffer para a próxima página física. - Monitorando PFNs dos pipe-buffers e page tables com o primitivo de leitura, o atacante garante que a página seguinte é uma página de PTE, converte a cópia OOB em escritas arbitrárias de PTE, e obtém leitura/escrita/execução irrestrita no kernel. O Chrome mitigou a exponibilidade bloqueando
MSG_OOBde renderers (6711812), e o Linux corrigiu a falha lógica em32ca245464e1além de introduzirCONFIG_AF_UNIX_OOBpara tornar a feature opcional.
Causa raiz: manage_oob() assume apenas um SKB de comprimento zero
unix_stream_read_generic() espera que todo SKB retornado por manage_oob() tenha unix_skb_len() > 0. Após 93c99f21db36, manage_oob() pulou o caminho de limpeza skb == u->oob_skb sempre que primeiro removia um SKB de comprimento zero deixado por recv(MSG_OOB). A correção subsequente (5aa57d9f2d53) ainda avançou do primeiro SKB de comprimento zero para skb_peek_next() sem re-verificar o comprimento. Com dois SKBs consecutivos de comprimento zero, a função retornou o segundo SKB vazio; unix_stream_read_generic() então o ignorou sem chamar manage_oob() novamente, de modo que o verdadeiro OOB SKB foi dequeued e freeado enquanto u->oob_skb ainda apontava para ele.
Sequência mínima de gatilhos
char byte;
int socks[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
for (int i = 0; i < 2; ++i) {
send(socks[1], "A", 1, MSG_OOB);
recv(socks[0], &byte, 1, MSG_OOB);
}
send(socks[1], "A", 1, MSG_OOB); // SKB3, u->oob_skb = SKB3
recv(socks[0], &byte, 1, 0); // normal recv frees SKB3
recv(socks[0], &byte, 1, MSG_OOB); // dangling u->oob_skb
Primitives exposed by unix_stream_recv_urg()
- Leitura arbitrária de 1 byte (repetível):
state->recv_actor()acaba realizandocopy_to_user(user, skb_sourced_addr, 1). Se o SKB pendente for realocado em memória controlada pelo atacante (ou em um alias controlado como uma página de pipe), cadarecv(MSG_OOB | MSG_PEEK)copia um byte de um endereço kernel arbitrário permitido por__check_object_size()para o espaço do usuário sem travar. ManterMSG_PEEKativado preserva o ponteiro pendente para leituras ilimitadas. - Escrita apenas parcialmente controlada: Quando
MSG_PEEKestá desativado,UNIXCB(oob_skb).consumed += 1incrementa o campo de 32 bits no deslocamento0x44. Em alocações SKB alinhadas a 0x100 isso fica quatro bytes acima de uma palavra alinhada a 8 bytes, convertendo o primitivo em um incremento de +4 GiB da palavra hospedada no offset0x40. Transformar isso em uma escrita kernel requer posicionar um valor 64-bit sensível naquele offset.
Reallocating the SKB page for arbitrary read
- Drain order-0/1 unmovable freelists: Mapear um VMA anônimo enorme read-only e falhar cada página para forçar a alocação de page-tables (order-0 unmovable). Preencher ~10% da RAM com page tables garante que alocações subsequentes de
skbuff_head_cacheusem páginas fresh do buddy uma vez que as listas order-0 se esgotem. - Spray SKBs and isolate a slab page: Use dezenas de stream socketpairs e enfileire centenas de pequenas mensagens por socket (~0x100 bytes por SKB) para popular
skbuff_head_cache. Free selecione SKBs para forçar uma página de slab alvo inteiramente sob controle do atacante e monitore o refcount destruct pageemergente via o primitivo de leitura. - Return the slab page to the buddy allocator: Free todos os objetos na página, então faça alocações/frees adicionais suficientes para empurrar a página para fora das listas parciais per-CPU do SLUB e das listas de páginas per-CPU para que ela se torne uma página order-1 na freelist do buddy.
- Reallocate as pipe buffer: Crie centenas de pipes; cada pipe reserva pelo menos duas páginas de dados de 0x1000 bytes (
PIPE_MIN_DEF_BUFFERS). Quando o buddy allocator split uma página order-1, metade reutiliza a página SKB liberada. Para localizar qual pipe e qual offset aliasoob_skb, escreva bytes marcadores únicos em fake SKBs armazenados por toda a páginas de pipe e executerecv(MSG_OOB | MSG_PEEK)repetidos até que o marcador seja retornado. - Forge a stable SKB layout: Popule a página de pipe aliasada com um fake
struct sk_buffcujos ponteirosdata/heade a estruturaskb_shared_infoapontam para endereços kernel arbitrários de interesse. Como x86_64 desabilita SMAP dentro decopy_to_user(), endereços em user-mode podem servir como buffers de staging até que os ponteiros kernel sejam conhecidos. - Respect usercopy hardening: O copy tem sucesso contra
.data/.bss, entradas vmemmap, ranges vmalloc per-CPU, stacks kernel de outras threads, e páginas direct-map que não atravessam limites de folio de ordem superior. Leituras contra.textou caches especializados rejeitadas por__check_heap_object()simplesmente retornam-EFAULTsem matar o processo.
Introspecting allocators with the read primitive
- Break KASLR: Leia qualquer descritor IDT do mapeamento fixo em
CPU_ENTRY_AREA_RO_IDT_VADDR(0xfffffe0000000000) e subtraia o deslocamento conhecido do handler para recuperar a base do kernel. - SLUB/buddy state: Símbolos globais em
.datarevelam bases dekmem_cache, enquanto entradas vmemmap expõem flags de tipo de cada página, ponteiro de freelist, e o cache dono. Escanear segmentos vmalloc per-CPU revela instâncias destruct kmem_cache_cpude modo que o próximo endereço de alocação de caches chave (ex.:skbuff_head_cache,kmalloc-cg-192) se torne previsível. - Page tables: Em vez de ler
mm_struct(bloqueado por usercopy), percorra apgd_listglobal (struct ptdesc) e case omm_structatual viacpu_tlbstate.loaded_mm. Uma vez que opgdraiz é conhecido, o primitivo pode atravessar todas as page tables para mapear PFNs para pipe buffers, page tables e stacks do kernel.
Recycling the SKB page as the top kernel-stack page
- Free a página de pipe controlada novamente e confirme via vmemmap que seu refcount retorna a zero.
- Imediatamente aloque quatro páginas de pipe auxiliares e então libere-as em ordem inversa para que o comportamento LIFO do buddy allocator seja determinístico.
- Chame
clone()para gerar uma thread auxiliar; stacks do Linux têm quatro páginas em x86_64, então as quatro páginas mais recentemente liberadas tornam-se sua stack, com a última página liberada (a antiga página SKB) nos endereços mais altos. - Verifique via page-table walk que o PFN do top stack da thread auxiliar é igual ao PFN da SKB reciclada.
- Use a leitura arbitrária para observar o layout da stack enquanto direciona a thread para
pipe_write().CONFIG_RANDOMIZE_KSTACK_OFFSETsubtrai um valor randômico de 0x0–0x3f0 (alinhado) deRSPpor syscall; escritas repetidas combinadas compoll()/read()de outra thread revelam quando o writer bloqueia com o offset desejado. Quando sortudo, o argumentobytesderramado decopy_page_from_iter()(R14) fica no offset0x40dentro da página reciclada.
Placing fake SKB metadata on the stack
- Use
sendmsg()em um socket AF_UNIX datagram: o kernel copia osockaddr_undo usuário para umsockaddr_storageresidente na stack (até 108 bytes) e os dados ancilares para outro buffer on-stack antes da syscall bloquear esperando espaço na fila. Isso permite plantar uma estrutura fake SKB precisa na memória da stack. - Detecte quando a cópia terminou fornecendo uma control message de 1 byte localizada em uma página de usuário não mapeada;
____sys_sendmsg()a faz faultar, então uma thread auxiliar fazendo poll emmincore()nesse endereço aprende quando a página de destino está presente. - O padding inicializado com zero por
CONFIG_INIT_STACK_ALL_ZEROpreenche convenientemente campos não usados, completando um header SKB válido sem escritas adicionais.
Timing the +4 GiB increment with a self-looping frag list
- Forge
skb_shinfo(fakeskb)->frag_listpara apontar para um segundo fake SKB (armazenado em memória user controlada) que temlen = 0enext = &self. Quandoskb_walk_frags()itera essa lista dentro de__skb_datagram_iter(), a execução gira indefinidamente porque o iterator nunca alcançaNULLe o loop de copy não faz progresso. - Mantenha a syscall recv rodando dentro do kernel deixando o segundo fake SKB em self-loop. Quando for hora de disparar o incremento, simplesmente mude o ponteiro
nextdo segundo SKB a partir do espaço usuário paraNULL. O loop sai eunix_stream_recv_urg()executa imediatamenteUNIXCB(oob_skb).consumed += 1uma vez, afetando qualquer objeto que atualmente ocupe a página de stack reciclada no offset0x40.
Stalling copy_from_iter() without userfaultfd
- Mapear um VMA anônimo RW gigante e faultá-lo completamente.
- Punch um hole de página única com
madvise(MADV_DONTNEED, hole, PAGE_SIZE)e coloque esse endereço dentro doiov_iterusado parawrite(pipefd, user_buf, 0x3000). - Em paralelo, chame
mprotect()em todo o VMA a partir de outra thread. A syscall pega o mmap write lock e anda por cada PTE. Quando o writer do pipe atinge o hole, o page fault handler bloqueia no mmap lock mantido pormprotect(), pausandocopy_from_iter()em um ponto determinístico enquanto o valorbytesderramado reside no segmento de stack hospedado pela página SKB reciclada.
Turning the increment into arbitrary PTE writes
- Fire the increment: Libere o frag loop enquanto
copy_from_iter()está parado para que o incremento de +4 GiB acerte a variávelbytes. - Overflow the copy: Uma vez que o fault retoma,
copy_page_from_iter()acredita que pode copiar >4 GiB para a página de pipe atual. Depois de preencher os legítimos 0x2000 bytes (duas pipe buffers), executa outra iteração e escreve os dados de usuário restantes na página física que segue o PFN do buffer de pipe. - Arrange adjacency: Usando telemetria do allocator, force o buddy allocator a colocar uma página de PTE de propriedade do processo imediatamente após a página de buffer do pipe alvo (ex.: alternar entre alocar páginas de pipe e tocar novos ranges virtuais para disparar alocação de page-tables até que os PFNs se alinhem dentro do mesmo pageblock de 2 MiB).
- Overwrite page tables: Encode entradas PTE desejadas nos 0x1000 bytes extras de dados de usuário para que o OOB
copy_from_iter()preencha a página vizinha com entradas escolhidas pelo atacante, concedendo mapeamentos user RW/RWX de memória física kernel ou reescrevendo entradas existentes para desabilitar SMEP/SMAP.
Mitigations / hardening ideas
- Kernel: Apply
32ca245464e1479bfea8592b9db227fdc1641705(properly revalidates SKBs) e considere desabilitar AF_UNIX OOB entirely a menos que estritamente necessário viaCONFIG_AF_UNIX_OOB(5155cbcdbf03). Hardenmanage_oob()com checagens adicionais de sanidade (ex.: loop atéunix_skb_len() > 0) e audite outros protocolos de socket para suposições similares. - Sandboxing: Filtrar flags
MSG_OOB/MSG_PEEKem perfis seccomp ou APIs broker de nível superior (mudança do Chrome6711812agora bloqueiaMSG_OOBdo lado do renderer). - Allocator defenses: Fortalecer randomização de freelist do SLUB ou impor page coloring por cache complicaria o reciclamento determinístico de páginas; limitar a contagem de pipe buffers também reduz a confiabilidade de realocação.
- Monitoring: Expor alocações de page-table em alta taxa ou uso anômalo de pipes via telemetria—este exploit consome grandes quantidades de page tables e pipe buffers.
References
- Project Zero – “From Chrome renderer code exec to kernel with MSG_OOB”
- Linux fix for CVE-2025-38236 (
manage_oobrevalidation) - Chromium CL 6711812 – block
MSG_OOBin renderers - Commit adding
CONFIG_AF_UNIX_OOBprompt
Tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporte o HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.


