
Bloqueando a cadeia de LPE DirtyFrag do Linux (CVE-2026-43284 / CVE-2026-43500) em tempo de execução com uma TracingPolicy do Cilium Tetragon.
Bloqueando a cadeia de escalonamento de privilégios do Linux DirtyFrag (CVE-2026-43284 / CVE-2026-43500) em tempo de execução com uma TracingPolicy do Cilium Tetragon. A política aplica SIGKILL à prova de conceito pública na etapa de configuração do socket, antes que ela alcance a escrita no page-cache que lhe daria root.
O que é isto. Um relatório de laboratório. Configurei o Tetragon pela primeira vez e queria ver se ele conseguiria realmente parar um LPE real e atual do kernel. Conseguiu. Este documento descreve exatamente o que executei, o que disparou e, igualmente importante, os limites do que isso prova. Não substitui a aplicação de patches.
Arquivos: este README (autocontido, com evidências inline) · block-dirtyfrag.yaml (a política)
| Exploit | DirtyFrag - PoC pública: V4bel/dirtyfrag |
| CVEs | CVE-2026-43284 (escrita no page-cache do xfrm-ESP), CVE-2026-43500 (escrita no page-cache do RxRPC) |
| Host | Ubuntu 24.04.4 LTS, Tetragon v1.7.0 (standalone, systemd) |
Baseline - sem política, kernel 6.8.0-88 (vulnerável) | PoC → uid=0(root) |
Com política - kernel 6.8.0-88 | PoC recebe SIGKILL em socket(AF_RXRPC); usuário permanece uid=1000 |
Kernel corrigido 6.8.0-134 | PoC falha sozinho (rc=4); o hook de socket ainda dispara, detecção, não mitigação (nota) |
| Natureza do controle | Controle compensatório / patch virtual, não uma correção de kernel |
O DirtyFrag é uma cadeia local de escalonamento de privilégios construída a partir de duas primitivas independentes de escrita no page-cache do kernel Linux: uma no caminho de descriptografia in-place do xfrm/ESP (IPsec) (CVE-2026-43284), e outra no caminho do RxRPC (CVE-2026-43500). Cada uma permite que um usuário local sem privilégios escreva bytes controlados pelo atacante em páginas do page-cache somente leitura, por exemplo, a imagem em cache de um binário setuid-root como /bin/su — e, a partir daí, obtenha root. A escrita atinge apenas a memória; o arquivo em disco nunca é modificado, portanto o monitoramento de integridade de arquivos não vê nada. É a mesma classe de bug do Dirty Pipe e do Copy Fail. As duas CVEs são encadeadas deliberadamente: se um caminho não estiver disponível em um determinado ambiente, o outro ainda funciona.
Neste host, os user namespaces sem privilégios são restringidos pelo AppArmor (kernel.apparmor_restrict_unprivileged_userns = 1), o que bloqueia a metade ESP da cadeia. Isso deixa o caminho RxRPC, que abre um socket AF_RXRPC (família de endereços 33) — e é esse passo que esta política mata.
A correção real é um kernel corrigido. Tudo aqui é um paliativo para um host que ainda não pode ser corrigido. O Ubuntu lançou correções para o DirtyFrag algum tempo antes deste teste; portanto, isto é um N-day conhecido, não um 0-day ao vivo — o objetivo é mostrar o que um controle em tempo de execução pode fazer em um host que, por qualquer motivo, ainda está executando um kernel vulnerável.
Política completa: block-dirtyfrag.yaml. Ela instala dois kprobes.
Hook 1 - sockets de preparação (sys_socket). O controle principal. O caminho RxRPC do exploit deve chamar socket(AF_RXRPC, …) em todas as execuções, independentemente de algum módulo do kernel já estar carregado. A política corresponde à família de endereços:
AF_RXRPC) → Sigkill. Em qualquer host que não seja um cliente AFS, praticamente nada legítimo abre um socket AF_RXRPC, então um kill generalizado aqui é seguro e de alta confiança.AF_ALG) → Post (apenas auditoria, sem kill). AF_ALG é a API de criptografia do kernel para userspace e tem usuários legítimos (cryptsetup, ferramentas libkcapi, alguns fluxos de trabalho FIPS). Matar apenas com base na família causaria falsos positivos, então este ramo apenas registra. O caminho para a aplicação é: auditar por um tempo, construir uma allowlist NotIn a partir dos binários realmente observados e então considerar promover para Sigkill.Hook 2 - carregamento automático de módulo vulnerável (security_kernel_module_request). Defesa em profundidade. Dispara quando o kernel é solicitado a carregar automaticamente uma família de módulos vulneráveis (esp4, esp6, rxrpc, os aliases de socket net-pf-33/net-pf-38, os templates de criptografia pcbc/fcrypt). Limitação: só dispara quando o módulo não já está residente — após a primeira execução do exploit em um boot, esses módulos são carregados e este hook fica silencioso. É uma camada de boot a frio, não o controle principal.
Juntos: o Hook 1 captura o exploit quer os módulos estejam quentes ou frios; o Hook 2 adiciona um disparo mais cedo e mais específico em um host frio.
6.8.0-88-generic, que é vulnerável (o cenário base atinge root). A máquina também tem o 6.8.0-134-generic instalado — o kernel corrigido — e isso foi confirmado diretamente: ao reiniciar no -134, o PoC falha sozinho (rc=4, sem root), com ou sem a política (veja a nota abaixo). Para reproduzir a demonstração de mitigação, inicialize o 6.8.0-88 pelo GRUB (ele ainda está instalado) — em qualquer kernel corrigido não há nada a mitigar.AF_ALG.lockdown,capability,landlock,yama,apparmor — sem bpf). Portanto, a aplicação usa Sigkill a partir do kprobe, não uma negação LSM dentro do kernel. O kill ocorre na syscall socket() — antes da primitiva de escrita — portanto, encerra o processo em uma etapa inicial obrigatória, em vez de "bloquear a vulnerabilidade" em si.Os passos 1–5 são todos do mesmo boot 6.8.0-88 (o kernel vulnerável).
1. Ambiente - Ubuntu 24.04.4, kernel 6.8.0-88-generic, Tetragon v1.7.0 ativo sob systemd, userns sem privilégios restrito.
$ uname -r
6.8.0-88-generic
$ tetra version
CLI version: v1.7.0
$ systemctl is-active tetragon
active
2. Pré-condições (política OFF) - ponto de partida limpo: nenhum módulo vulnerável residente, nenhum estado xfrm, nenhuma política carregada.
$ lsmod | grep -E 'esp4|esp6|rxrpc' || echo "(none loaded)"
(none loaded)
$ sudo ip xfrm state # (empty)
$ sudo tetra tp list
ID NAME STATE FILTERID NAMESPACE SENSORS KERNELMEMORY MODE NPOST NENFORCE NMONITOR
3. Baseline (política OFF) - o exploit funciona. Isto é o que prova que o kernel está realmente vulnerável; todo o relatório se baseia nisso.
$ ./exp ; id
uid=0(root) gid=0(root) groups=0(root)
4. Carregue a política.
$ sudo tetra tp add /etc/tetragon/tetragon.tp.d/block-dirtyfrag.yaml
tracing policy "…/block-dirtyfrag.yaml" added
5. Com aplicação (política ON) - o kill. O exploit é encerrado por sinal na chamada socket() e nunca chega ao su:
🚀 process /home/…/dirtyfrag/exp
❓ syscall /home/…/dirtyfrag/exp __x64_sys_socket
Kernel:
0x0: __x64_sys_socket+0x5
0x0: do_syscall_64+0x7f
0x0: entry_SYSCALL_64_after_hwframe+0x78
💥 exit /home/…/dirtyfrag/exp SIGKILL
O evento estruturado confirma tanto a correspondência quanto o kill — um process_kprobe na syscall de socket e depois um process_exit por sinal para o mesmo PID:
// process_kprobe — the AF_RXRPC match
{ "process_kprobe": {
"process": { "pid": 24083, "uid": 1000, "binary": ".../dirtyfrag/exp" },
"function_name": "__x64_sys_socket",
"args": [ { "int_arg": 33, "label": "family" } ],
"policy_name": "block-dirtyfrag",
"action": "KPROBE_ACTION_POST"
} }
// process_exit — same PID, killed by signal
{ "process_exit": {
"process": { "pid": 24083, "binary": ".../dirtyfrag/exp" },
"signal": "SIGKILL"
} }
O action do kprobe aparece como KPROBE_ACTION_POST porque a ação Post é a que emite o evento visível; a ação Sigkill é a que produz a saída SIGKILL separada. Os dois eventos juntos são a prova, a correspondência e o kill.
Nota: a captura bruta
07também carrega uma stringmessagede uma revisão anterior da política (antes de o ramoAF_ALGser separado para auditoria apenas). A correspondênciafamily: 33e oSIGKILLresultante são idênticos entre as revisões; apenas esse texto difere.
Depois da execução no kernel vulnerável, o host foi reiniciado no 6.8.0-134-generic (o kernel corrigido do Ubuntu) e o PoC foi executado novamente:
$ ./exp # policy OFF
dirtyfrag: failed (rc=4) # exploit fails on its own — no root
$ ./exp # policy ON
Killed # SIGKILL at socket(AF_RXRPC)
Sendo preciso sobre o que isto mostra:
rc=4, sem root) porque o kernel está corrigido. Não há nada para a política impedir. O antes/depois que sustenta a alegação de mitigação existe apenas no kernel vulnerável -88.socket(AF_RXRPC), portanto o Tetragon ainda aplica SIGKILL e registra a tentativa no log, tanto em um kernel corrigido quanto em um não corrigido. Isso tem valor como detecção de uma tentativa e como defesa em profundidade, mas não é o mesmo que impedir um exploit funcional.AF_RXRPC é legítimo e um kill generalizado o quebraria. Verifique por nó.AF_ALG é somente auditoria por design. Uma variante usando apenas o caminho AF_ALG com módulos já quentes seria registrada, não morta, até você promover esse ramo para aplicação (após allowlist).AF_RXRPC é aberto antes da primitiva de escrita. Essa ordenação é o que torna um kill precoce eficaz; não é uma garantia sobre todos os exploits possíveis.Configuração: Tetragon v1.7.0 standalone, iniciado via systemctl; políticas carregadas com tetra tracingpolicy add.