
Portabilidade multiplataforma em C do Copy Fail Linux LPE (CVE-2026-31431). Divulgado em 2026-04-29 por Theori / Xint.
English (en) ∙ 日本語 (ja) ∙ 简体中文 (zh-cn) ∙ 한국어 (ko) ∙ Русский (ru)
Uma reimplementação em C multiplataforma do LPE (Escalada de Privilégio Local) do Linux Copy Fail (CVE-2026-31431), divulgado em 2026-04-29 por Theori / Xint. Veja o writeup canônico em copy.fail para a descrição completa da vulnerabilidade, cronograma e processo de descoberta da Theori.
O proof-of-concept lançado publicamente é um script Python de 732 bytes. Esta porta em C demonstra que o mesmo exploit pode ser expresso como C portátil compilável para qualquer arquitetura que o nolibc suporta, sem blobs hex por arquitetura ou assembly inline na própria fonte do projeto.
Autor desta porta: Tony Gies [email protected]. Descoberta e divulgação original: Theori / Xint.
copy-fail-c/
├── exploit.c o dropper (variante de mutação binária)
├── exploit-passwd.c o dropper (variante de inversão de UID do /etc/passwd)
├── vulnerable.c verificador de vulnerabilidade não destrutivo
├── payload.c o corpo que é dropado (setgid+setuid+execve sh)
├── utils.c, utils.h primitiva de mutação de cache de página compartilhada AF_ALG/splice
├── Makefile orquestração de compilação
├── nolibc/ fornecido de torvalds/linux tools/include/nolibc
└── README.md este arquivo
Após make:
├── payload tiny static ELF, embedded into the dropper as bytes
├── payload.o payload wrapped as a relocatable .o by `ld -r -b binary`
├── exploit dropper, variante de mutação binária
├── exploit-passwd dropper, variante de inversão de UID do /etc/passwd
└── vulnerable verificador de vulnerabilidade não destrutivo
exploit.c abre o binário alvo como somente leitura, então para cada janela de 4 bytes do payload embutido executa uma AEAD-decrypt falsa através do AF_ALG cuja entrada de ciphertext é fornecida via splice() a partir das páginas de cache de página do alvo. A otimização in-place do template authencesn trata as páginas de origem spliceadas tanto como entrada de ciphertext quanto como destino de plaintext, então o decrypt (com falha) já sobrescreveu a página de cache de página no momento em que a verificação de autenticação rejeita a requisição. Após 4 * N iterações a imagem em cache do alvo foi substituída byte a byte pelo payload. Executar execve() no alvo carrega as páginas mutadas; o inode no disco ainda é setuid root, então o kernel concede credenciais de root e executa o payload.
payload.c é C portátil simples: setgid(0); setuid(0); execve("/bin/sh", ...). nolibc fornece o _start, a maquinaria de syscall e o malabarismo de registradores por arquitetura.
Uma segunda variante, exploit-passwd.c, muta quatro bytes do cache de página do /etc/passwd em vez da imagem de um binário setuid. Ela não precisa de payload embutido e funciona em sistemas onde a rota de mutação binária está bloqueada, mas sua superfície de cashout é muito mais estreita.
vulnerable.c não é um exploit. Ele cria um testfile local contendo a string init, então executa a mesma primitiva patch_chunk() contra o cache de página do próprio arquivo para sobrescrever os bytes com vulnerable. Se o conteúdo lido de volta corresponder, o kernel em execução está na janela do CVE-2026-31431. O inode no disco nunca é modificado; testfile é removido na saída; a mutação do cache de página evapora com ele. Executa sem privilégios. Sai com 100 se vulnerável, 0 se a primitiva executou mas a mutação não ocorreu, 2 se a família de socket AF_ALG ou o template authencesn estiver indisponível, então o estado de patch não pode ser determinado, e 1 para outros erros em tempo de execução.
Padrão (nativo da arquitetura do host):
make
Compilação cruzada para aarch64 (ou qualquer outra arquitetura Linux para a qual um cross-toolchain está instalado):
make CC=aarch64-linux-gnu-gcc LD=aarch64-linux-gnu-ld
Arquiteturas suportadas pelo nolibc fornecido (de acordo com upstream): x86_64, i386, arm, aarch64, riscv32/64, mips, ppc, s390x, loongarch, m68k, sh, sparc. O nolibc despacha com base nas macros de arquitetura do compilador, então escolher o CC/LD correto é suficiente.
Necessário para compilar:
cc, gcc, ou qualquer variante cruzada)ld -r -b binary (ld do binutils e lld ambos suportam)linux/if_alg.h e <asm/unistd.h> (Debian/Ubuntu: linux-libc-dev; variantes cruzadas: normalmente trazidas pelo pacote cross-toolchain)Conjuntos de cabeçalhos anteriores ao Linux 5.6 são anteriores a __kernel_old_time_t e struct __kernel_old_timespec, que o nolibc fornecido usa. compat.h (forçadamente incluído na compilação do payload) os fornece quando ausentes, então um linux-libc-dev mais antigo ainda compila. É um no-op em cabeçalhos 5.6+.
Não há dependências de bibliotecas externas. O payload é compilado freestanding contra o nolibc; o dropper faz link contra a libc do host apenas para fprintf e perror.
A few small toolchain features carry most of the weight in keeping the source portable and the payload small.
nolibc/ é a substituição minúscula de libc apenas com cabeçalhos do kernel, fornecida de torvalds/linux tools/include/nolibc/. Ela fornece _start, uma macro syscall() portátil e wrappers inline de syscall, com as convenções de registradores por arquitetura codificadas em nolibc/arch-*.h. Compilar o payload com -nostdlib -static -ffreestanding -Inolibc produz um tiny static ELF que chama o kernel diretamente sem arrastar a inicialização do glibc, TLS init ou encanamento de stack-canary. Resultado após empacotamento e remoção de seções (ambos abaixo): ~720 bytes no x86_64, ~1.2 KB no aarch64, versus ~17 KB para o mesmo payload.c linkado contra musl-static ou ~700 KB contra glibc-static.
ld -r -b binary para embutirO Makefile transforma o payload ELF construído em payload.o via ld -r -b binary -o payload.o payload. O linker emite os bytes de entrada textualmente como a seção de dados de um arquivo objeto relocável e sintetiza três símbolos a partir do nome do arquivo de entrada:
_binary_payload_start endereço do primeiro byte do payload
_binary_payload_end endereço um byte após o último byte do payload
_binary_payload_size símbolo absoluto cujo valor é o tamanho em bytes
exploit.c declara os dois primeiros como extern const unsigned char[] e calcula o tamanho como _binary_payload_end - _binary_payload_start.
-Wl,-N mais max-page-size apertadoO payload é linkado estaticamente com -Wl,-N -Wl,-z,max-page-size=0x10, que colapsa .text/.rodata/.data em um único segmento LOAD com alinhamento de arquivo de 16 bytes em vez do padrão de 4 KB por segmento alinhado à página do kernel. Isso produz um aviso "RWX permissions" do ld, que é apenas informacional – a proteção de memória em tempo de execução do payload não importa para seu programa de propósito único. Sem essa flag, o mesmo código linka para ~13 KB no x86_64 (principalmente preenchimento zero entre segmentos); com ela, ~1.3 KB antes da remoção do cabeçalho de seção abaixo.
Após o link, objcopy --strip-section-headers remove a tabela de cabeçalhos de seção do payload e .shstrtab. O carregador ELF do kernel mapeia um programa a partir de seus cabeçalhos de programa sozinhos, então esses bytes nunca são carregados em tempo de execução e, como payload é embutido textualmente, eles também inflam o drop e a contagem de iterações patch_chunk. Removê-los reduz o payload x86_64 de ~1.3 KB para 720 bytes (de 322 iterações de quatro bytes para 180). Duas flags de link diminuem o resto: -Wl,--build-id=none remove a nota de build-id, e -fcf-protection=none remove a nota CET x86 onde o compilador suporta.
O strip precisa de binutils >= 2.40. Compilações cruzadas passam o objcopy alvo via OBJCOPY= (ex.: OBJCOPY=aarch64-linux-gnu-objcopy); quando objcopy não consegue fazer, a compilação imprime uma nota e mantém um payload válido maior.
Este repositório fornece duas variantes de exploit que compartilham a primitiva de mutação de cache de página AF_ALG/splice, mas cashoutam em execução de root de forma diferente. Seus perfis de confiabilidade não são equivalentes, e a diferença importa ao raciocinar sobre modelos de ameaça do mundo real.
exploit)Muta o cache de página de um binário setuid alvo com os bytes do payload embutido, então executa o binário. O kernel concede credenciais de root a partir do bit setuid intacto do binário no disco, carrega a imagem corrompida em memória e executa o payload.
Funciona onde quer que o atacante possa open(target, O_RDONLY) para qualquer binário setuid root no sistema. Mais ou menos derrotada por ambientes que limitam binários setuid a diretórios de leitura restrita e por designs de sistema sem setuid.
exploit-passwd)Muta quatro bytes do cache de página do /etc/passwd para definir o campo UID do usuário em execução para "0000". /etc/passwd é legível mundialmente em todo sistema Linux padrão, então a mutação é universal. Traduzi-la em execução de root depende de algum processo do lado root que resolve o usuário via getpwnam/getpwuid e age com base no uid resolvido sem validação cruzada. Muitos consumidores existem; muitos deles verificam defensivamente contra a visão do kernel do uid chamador ou contra a propriedade do arquivo no disco, quebrando o cashout.
suexploit-passwd executa su <user> após mutar, como o cashout mais simples possível. Isso funciona contra util-linux su mas falha contra shadow-utils su com "Cannot determine your user name." A mutação do cache de página ainda está em vigor nesse ponto, e pivotear para qualquer outro cashout (ex.: usando um daemon que resolve usuários via getpwnam sem verificação cruzada) é possível nesse ponto. Execute echo 3 > /proc/sys/vm/drop_caches como root para limpar o cache de página corrompido quando terminar os testes.
floor: torvalds/linux 72548b093ee3 Agosto de 2017, v4.14
(reestruturação do iov_iter do AF_ALG que
introduziu a primitiva de escrita em página de arquivo
via splice no scatterlist do AEAD)
ceiling: torvalds/linux a664bf3d603d Abril de 2026, mainline
(reverte a otimização in-place do algif_aead de 2017;
separa os scatterlists de origem e destino
para que páginas de cache de página não possam mais
ser um destino criptográfico gravável)
No meio: todo kernel de distribuição importante que não aplicou o backport da correção. Ubuntu, RHEL, SUSE, Amazon Linux e Debian foram todos confirmados vulneráveis em suas imagens de kernel de nuvem padrão no momento da divulgação. Backports em nível de distribuição começaram a ser lançados por volta de 2026-04-29 juntamente com a divulgação pública. Para verificar se um kernel alvo está na janela, verifique se a664bf3d603d (ou seu backport específico da distribuição) está presente no git log do kernel ou no changelog da distribuição.
Para revisão de segurança paga, trabalho de portabilidade personalizado ou avisos de segurança privados, entre em contato com o autor através de sua consultoria, Crash United, LLC.
Contato: [email protected] · https://crashunited.com
GitHub: @tgies · X/Twitter: @me_irl
Descoberta e divulgação original do CVE-2026-31431: Theori / Xint. Writeup público: https://copy.fail/.
Esta porta em C: Tony Gies [email protected]
nolibc/: fornecido da árvore do kernel Linux, licenciado duplamente LGPL-2.1-ou-superior OU MIT (veja nolibc/nolibc.h e cabeçalhos SPDX individuais).
As fontes do dropper e payload neste repositório são lançadas sob os mesmos termos duplos LGPL-2.1-ou-superior OU MIT que a árvore nolibc da qual dependem, para manter o licenciamento trivialmente compatível para qualquer um que forneça este diretório inteiro em seu próprio trabalho.
O exploit e o payload são publicados para fins de pesquisa de segurança e detecção defensiva. O uso contra sistemas que você não possui ou para os quais não tem autorização explícita para testar é problema seu, não do autor.
| Cashout | Configuração pré-root necessária | Notas |
|---|
| Spawn de sessão WSL2 | Não | O setuid(getpwnam(default_user)->pw_uid) por sessão do WSL não faz validação. Funciona perfeitamente. |
util-linux su | Não | Tratamento permissivo de identidade do chamador. |
shadow-utils su | Sim | A verificação de identidade do chamador getpwuid(getuid()) falha porque a mutação desmapeia o uid real. |
sshd (StrictModes yes padrão) | Sim (desabilitar StrictModes) | StrictModes exige que o diretório home seja propriedade de root ou pw->pw_uid. A mutação faz pw_uid=0; o proprietário no disco permanece no uid original; incompatibilidade recusa autenticação. |
| Entrega local MTA (postfix, exim, etc.) | Variável | Depende da validação de permissões de home do MDA. Testar por MTA. |