
Exploit de prova de conceito para escalonamento local de privilégios em uma condição de corrida na rate-table do qdisc do Linux, usando heap grooming com BPF e um vazamento via pipe para obter root.

Este repositório contém uma prova de conceito de exploração de escalonamento local de privilégios para a CVE-2026-68138, uma condição de corrida no código da tabela de taxas do controlador de tráfego do Linux. No ambiente QEMU testado, o PoC eleva um processo comum com UID externo 1000 a um shell com UID 0 no namespace de usuário inicial.
Aviso
Este código corrompe intencionalmente o estado da memória do kernel. Use-o somente em uma VM isolada e descartável que lhe pertença. Uma corrida perdida ou uma limpeza prematura pode causar pânico no convidado. Não o execute em uma estação de trabalho, servidor ou sistema de terceiros.
qdisc_get_rtab() e qdisc_put_rtab() gerenciam uma lista encadeada
singular global ao processo, qdisc_rtab_list, e um int refcnt simples e
não atômico. Historicamente, os chamadores mantinham o mutex RTNL, que
serializava o acesso à lista e à contagem de referências.
O classificador flower define TCF_PROTO_OPS_DOIT_UNLOCKED. Uma requisição
RTM_NEWTFILTER para uma regra flower pode, consequentemente, alcançar uma
ação de polícia e chamar os auxiliares da tabela de taxas do qdisc sem RTNL:
tc_new_tfilter()
-> fl_change()
-> tcf_exts_validate_ex()
-> tcf_action_init()
-> tcf_police_init()
-> qdisc_get_rtab()/qdisc_put_rtab()
Requisições concorrentes que usam a mesma tabela de taxas podem competir pela
lista global e por sua contagem de referências. O resultado é um
use-after-free ou double-free de struct qdisc_rate_table, um objeto de 1056
bytes alocado de kmalloc-2k no kernel x86-64 testado.
Como a lista é global em vez de por namespace de rede, requisições de namespaces de rede separados ainda competem pelo mesmo objeto.
O registro CNA do Linux identifica o commit de introdução
470502de5bdb,
lançado no Linux 5.1.
Kernels de distribuições frequentemente aplicam correções por backport sem alterar a versão upstream mostrada acima. Verifique se algum dos commits de correção — ou a alteração equivalente do spinlock da tabela de taxas do qdisc — está presente no código-fonte exato do kernel usado pelo sistema.
A exploração foi desenvolvida e validada contra o commit vulnerável
92d3817649df2b0b6a008a686c8275c88d7ef594, o pai direto da correção do
mainline. O controle com kernel corrigido usou
f43ee0c0730d6191629b5ee1ceae27b1ebfdc047.
Em 2026-08-12, a
pesquisa do rastreador de CVEs do Ubuntu
não retornou uma entrada para esta CVE. A tabela abaixo é, portanto, uma
inspeção direta do código-fonte, não uma determinação de status de segurança
da Canonical. Cada tag Ubuntu vinculada ainda possui a qdisc_rtab_list sem
bloqueio e não contém a correção qdisc_rtab_lock.
Nenhum pacote Ubuntu corrigido foi identificado na data dessa inspeção. Pacotes Ubuntu futuros devem ser verificados quanto a um backport equivalente às correções upstream vinculadas, e não avaliados apenas pelo número de versão.
A exploração específica para a build do Ubuntu 22.04, o laboratório QEMU, a
soma de verificação exata da imagem e as condições estão documentados em
ubuntu/README.md. Ela foi validada contra o kernel
oficial 5.15.0-187-generic #197-Ubuntu com contabilização de cgroup de
memória ativada.
O bug subjacente e esta cadeia de exploração específica têm requisitos diferentes. O PoC foi validado com:
CONFIG_USER_NS=y e CONFIG_NET_NS=y;CONFIG_NET_CLS=y, CONFIG_NET_CLS_FLOWER=y,
CONFIG_NET_CLS_ACT=y e CONFIG_NET_ACT_POLICE=y;CONFIG_TMPFS_XATTR=y para o heap spray com simple_xattr;CONFIG_MODULES=y e um /sbin/modprobe utilizável para o auxiliar final de root;CONFIG_MEMCG=n, para que as alocações qdisc/BPF/pipe/xattr usadas por esta
cadeia compartilhem o cache kmalloc-2k esperado;CONFIG_SLAB_BUCKETS=y, randomização de freelist e endurecimento de freelist
estavam ativados no kernel de teste bem-sucedido. O KASLR não é contornado
inerentemente com um endereço fixo: o PoC obtém o ponteiro de página e os
ponteiros de operações necessários por meio do vazamento de pipe. O laboratório
fornecido usou nokaslr para simplificar a depuração.
Kernels com contabilização de cgroup de memória ativada ou com layouts de alocador/cache diferentes exigem uma estratégia de reaproveitamento diferente. O PoC deliberadamente se recusa a afirmar portabilidade entre configurações arbitrárias de distribuições.
A variante separada do Ubuntu implementa essa estratégia de reaproveitamento
diferente; seus requisitos são intencionalmente mais restritos e estão
listados em ubuntu/README.md.
Quatro threads de trabalho entram em namespaces de rede distintos e criam filtros flower com ações de polícia. Três workers seguem o caminho de ação bem- sucedida; um fornece um estimador deliberadamente inválido após adquirir ambas as referências da tabela de taxas, forçando o caminho de limpeza. Essa combinação torna a manipulação concorrente da contagem de referências e da lista reproduzível sem compartilhar o estado do classificador flower entre os workers.
qdisc_rate_table com BPF clássicoApós cada requisição netlink, a mesma CPU imediatamente anexa um filtro BPF
clássico de 133 instruções. Sua matriz de instruções de 1064 bytes é alocada
de kmalloc-2k e é moldada para que os bytes que se sobrepõem a
qdisc_rate_table.next e qdisc_rate_table.refcnt permaneçam inicialmente
válidos.
A instrução final contém um marcador por socket. SO_GET_FILTER permite que o
PoC detecte quando o ponteiro orig_prog->filter de um socket foi redirecionado
para outra alocação BPF ativa. Isso produz dois objetos de socket que se
referem ao mesmo buffer de instruções.
Fechar um dos proprietários adia a liberação real por meio de
sk_filter_release_rcu(). Após o período de graça, o PoC aloca anéis de pipe
com 32 slots:
32 * sizeof(struct pipe_buffer) = 32 * 40 = 1280 bytes -> kmalloc-2k
Um anel reivindica o buffer BPF com aliase. Ler o outro socket com
SO_GET_FILTER revela um pipe_buffer ativo, incluindo seus ponteiros page
e ops e o deslocamento da entrada de page cache de /sbin/modprobe.
Fechar o segundo socket libera o anel de pipe ativo após outro período de
graça de RCU, enquanto o pipe_inode_info correspondente ainda faz referência
a ele. Um spray de simple_xattr reivindica o anel de 1280 bytes e coloca o
pipe_buffer vazado no slot esperado com PIPE_BUF_FLAG_CAN_MERGE definido.
Escrever em cada pipe candidato eventualmente anexa bytes controlados pelo
atacante à página de page cache que serve de base ao ponto de entrada do ELF de
/sbin/modprobe. O PoC lê o arquivo de volta e só prossegue depois de
confirmar a sobrescrita exata.
Kernels recentes não usam mais o fallback histórico de autoload de módulo por
binfmt inválido em search_binary_handler(). Este PoC, em vez disso, cria um
socket de datagrama AF_INET com o protocolo não suportado 253. inet_create()
solicita o módulo ausente net-pf-2-proto-253-type-2, fazendo com que o
kernel execute o /sbin/modprobe sobrescrito como root do namespace inicial.
O payload injetado no ponto de entrada grava um arquivo de prova e abre um shell root no console serial do QEMU.
O binário convidado é vinculado estaticamente:
./build.sh
Comando equivalente:
gcc -O2 -static -pthread -Wall -Wextra -Werror \
-o build/exploit exploit.c
O binário resultante deve ser exposto dentro do convidado como
/exploit/CVE-2026-68138. O laboratório testado usou o compartilhamento 9p de
leitura e gravação do QEMU. O auxiliar de root espera esse compartilhamento em
/chroot/exploit a partir do namespace de montagem inicial; ajuste
helper_command em exploit.c se o laboratório usar outro layout.
As opções relevantes do kernel de teste estão registradas em
config.fragment. Elas são um fragmento, não uma
configuração completa de kernel de produção.
A partir do shell convidado sem privilégios:
id
/exploit/CVE-2026-68138
A corrida é probabilística. Em uma falha, o PoC imprime:
[-] no BPF alias found in this attempt
Reinicie a VM antes de tentar novamente. Não o execute novamente no mesmo convidado: a lista global de qdisc já pode ter sido corrompida.
Uma execução bem-sucedida atinge:
[+] confirmed dangling orig_prog->filter pointer
[+] pipe leak: page=<kernel pointer> ops=<kernel pointer> off=0x60f len=1 flags=0
[+] page-cache overwrite via dangling pipe
[+] controlled page-cache write confirmed; triggering helper
[+] CVE-2026-68138 initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
[+] ROOT PROOF: uid=0(root) gid=0(root) groups=0(root)
A transcrição completa e saneada está em
docs/example-output.txt.
A cadeia completa foi concluída três vezes a partir de inicializações novas da VM vulnerável. O binário final produziu:
outer identity: uid=1000(user)
alias: classic-BPF orig_prog->filter overlap
leak: live pipe_buffer page and ops pointers
write: verified /sbin/modprobe page-cache modification
final identity: uid=0(root) gid=0(root) groups=0(root)
O mesmo binário foi executado contra o commit corrigido com a mesma topologia de quatro vCPUs. Aproximadamente 126.000 requisições de corrida foram concluídas sem uma aliase de BPF, e nenhum estágio de pipe ou page cache foi alcançado.
A variante específica do Ubuntu também foi executada contra o kernel genérico
oficial exato 5.15.0-187.197. O convidado inicializou com quatro vCPUs,
contabilização normal de cgroup de memória e nokaslr como o único argumento
de kernel específico da exploração.
O kernel do Ubuntu não foi recompilado: o teste usou o
vmlinuz-5.15.0-187-generic inalterado da distribuição e os módulos oficiais
correspondentes. A
tabela de pré-requisitos do Ubuntu
separa os recursos do kernel de estoque dos ajustes de tempo de execução e de
laboratório QEMU.
A conta inicial não tinha acesso sudo nem grupos suplementares:
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
A partir de um snapshot novo, a exploração obteve a aliase de BPF, reivindicou-a
por meio de objetos simple_xattr do cgroup-v2 e bitmaps de select() com
bloqueio, verificou que /proc/sys/kernel/modprobe continha /tmp/x e
alcançou:
[+] CVE-2026-68138 Ubuntu initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
root@cve-2026-68138-jammy:/# id
uid=0(root) gid=0(root) groups=0(root)
Consulte a transcrição QEMU real e as
instruções do laboratório Ubuntu. A variante usa endereço
fixo para modprobe_path deste pacote e, portanto, exige nokaslr; ela não
afirma contornar o KASLR nem ser compatível com outra build do Ubuntu.
O patch já faz parte do kernel Linux e, portanto, não é duplicado neste repositório. Os commits canônicos de correção estável e do mainline estão vinculados nas referências abaixo.
Pesquisa e implementação da exploração: A. Ramos <[email protected]>
(Twitter: @aramosf).
| Linha | Status | Commit/versão |
|---|
| Linux antes de 5.1 | Não afetado | A alteração introdutória está ausente |
| Linux 5.1 até 7.1.5 | Afetado, salvo se houver backport do fornecedor | 470502de5bdb até o commit anterior à correção estável |
| Linux 7.1.y | Corrigido | 7.1.6, fb29e1b41052 |
| Série de desenvolvimento do Linux 7.2 | Afetado antes do rc5 | rc1 até rc4 |
| Mainline | Corrigido | 7.2-rc5, f43ee0c0730d |
| Linha do Ubuntu | Pacote/tag inspecionado | Resultado do código-fonte |
|---|
| Ubuntu 22.04 GA | 5.15.0-187.197 | Código vulnerável presente; exploração completa reproduzida no QEMU |
| Ubuntu 22.04 HWE | 6.8.0-136.136~22.04.1 | Código vulnerável presente; cadeia de exploração não testada |
| Ubuntu 24.04 HWE | 7.0.0-28.28~24.04.1 | Código vulnerável presente; esta cadeia de exploração não é compatível com o endurecimento do alocador dela |
| Ubuntu 26.04 | 7.0.0-28.28 | Código vulnerável presente; esta cadeia de exploração não é compatível com o endurecimento do alocador dela |