Odeio compiladores
(xeiaso.net)- O Anubis está projetando expandir a prova de trabalho de proteção de sites para além do SHA-256, fazendo com que cliente e servidor executem a mesma lógica de verificação em WebAssembly
- Para não excluir ambientes com WebAssembly desativado, foi criado um caminho de recompilação para JavaScript, mas ele é mais lento que WebAssembly e pode ficar ainda mais lento se o JIT também estiver desativado
- O
wasm2jsdas distribuições Linux estava desatualizado e gerava saídas diferentes da versão do Homebrew, então, para builds reproduzíveis, passou-se a incluir em bundle umwasm2jscompilado com owasi-sdk - Builds em C/C++ podem ter saída variável em nível de byte mesmo com a mesma entrada, por causa de
__DATE__,__TIME__, dowasm-optno$PATHe da ordem de ponteiros no código de tratamento de exceções - A implementação final garantiu determinismo dentro de cada arquitetura com
--no-wasm-opt,setarch --addr-no-randomize, verificação de SHA-256 por x86_64 e arm64 e checagem de rebuild em CI
A prova de trabalho em WebAssembly do Anubis e o caminho alternativo em JavaScript
- O Anubis quer adicionar uma verificação de proof-of-work baseada em WebAssembly para que administradores possam usar métodos de prova de trabalho que não sejam SHA-256 na proteção de sites
- O objetivo central é não implementar a lógica de verificação separadamente no cliente e no servidor, mas defini-la em um único lugar
- Cliente e servidor se conectam ao mesmo WebAssembly para executar a lógica de verificação
- A estrutura busca garantir que ambos operem em lockstep
- Clientes com WebAssembly desativado também entram no escopo
- Há a restrição de não querer, na prática, excluir usuários do site
- O Anubis precisa equilibrar experiência do usuário, experiência do administrador e experiência do desenvolvedor
- O desvio escolhido foi recompilar WebAssembly para JavaScript
- Inspirado em The Birth and Death of JavaScript
- O JavaScript resultante é mais lento que o WebAssembly equivalente
- Ao desativar WebAssembly, em alguns casos o JIT de JavaScript também é desativado, o que pode deixá-lo ainda mais lento
- Ainda é preciso pesquisar melhor se isso é mais eficiente que o JavaScript existente em hardware de baixo desempenho
Por que foi preciso incluir wasm2js em bundle
- A ferramenta necessária é o
wasm2jsdo projeto binaryen - O
wasm2jsexiste como pacote em distribuições Linux, mas a versão da distribuição estava antiga e não conseguia gerar a mesma saída da versão do Homebrew usada no ambiente de desenvolvimento - Para builds reproduzíveis, a deterministicidade da saída é essencial
- Para que usuários e empacotadores possam confiar no binário
wasm2jsversionado no repositório do Anubis, eles precisam conseguir compilar a mesma versão por conta própria e obter exatamente os mesmos bytes - Idealmente, esses mesmos bytes também deveriam sair nas máquinas de outras pessoas
- Para que usuários e empacotadores possam confiar no binário
- Para isso, foi incluída uma cópia do
wasm2jscompilada com o wasi-sdk para o alvo WebAssembly
Onde a reprodutibilidade se quebra com facilidade em builds C/C++
- Mesmo usando os mesmos bytes de código-fonte e a mesma entrada, a saída do compilador nem sempre resulta nos mesmos bytes
- Em C/C++, só os macros embutidos
__DATE__e__TIME__já podem produzir saída não determinística- O exemplo
hello.cppfoi escrito para imprimir a data e a hora do build - Um build imprimiu
Jun 18 2026 00:00:59, e outro imprimiuJun 18 2026 00:01:11 - Os bytes do código-fonte eram os mesmos, mas a saída do compilador foi diferente
- O exemplo
- Em teoria, compiladores pequenos poderiam ser determinísticos, mas compiladores reais têm muito mais variáveis complexas
O problema de o Clang executar silenciosamente o wasm-opt do $PATH
- Além do
wasm2js, o binaryen também inclui owasm-opt, que otimiza a saída de compiladores WebAssembly - Durante o build, o Clang chama o
wasm-optvia shell out- Em geral, esse é um comportamento razoável para melhorar desempenho
- Aqui, porém, a diferença de versão do
wasm-optpresente no$PATHquebrou a reprodutibilidade
- O
wasm-optdo DGX Spark era a versão 108 em/usr/bin/wasm-opt, enquanto owasm-optdo Homebrew na workstation era a versão 130 - O
wasi-sdke o binaryen dependem da extensão WebAssembly Exceptions- Segundo o Can I use, 93,86% dos usuários de navegadores usam engines com suporte a isso
- C++ é uma linguagem que usa muitas exceções, então o tratamento nativo de exceções em WebAssembly pode reduzir boilerplate
- O wasmtime e o wazero exigem ativação explícita do suporte a exceções
- No wasmtime, é possível passar
-W exceptions=y - No wazero, é necessário um runner harness customizado
- No wasmtime, é possível passar
- Em uma máquina arm, o
wasm-optantigo encontrava instruções de exceção e encerrava, fazendo o build falhar - Ao passar
--no-wasm-optna etapa de linkedição, esse caminho de não reprodutibilidade foi removido
O impacto da disposição de endereços na geração de código de tratamento de exceções
- A versão do Clang em uso mostrou geração de código sensível a endereços no caminho de tratamento de exceções durante a compilação do
wasm2js - Valores brutos de ponteiro afetavam a ordem de saída de alguns blocos
try_table- Isso gerava uma diferença de cerca de 29 bytes a cada build
- O cálculo em si era quase o mesmo, mas a ordem dos bytes mudava e as referências de
catchtambém
- Mesmo compilando a mesma versão fixa do
wasm2jsem uma máquina arm64, a ordem de iteração dos ponteiros diferia da workstation, causando o mesmo problema - Foram adotados dois contornos
- Desativar a randomização do espaço de endereços desse build com
setarch --addr-no-randomize - Gerar checksums SHA-256 known-good separados para x86_64 e arm64 em máquinas confiáveis
- Desativar a randomização do espaço de endereços desse build com
- O CI executa
./build.shem./utils/wasm/wasm2jse depois verifica os checksums- Se bater com
shasums.x86_64, o checksum x86_64 é considerado aprovado - Se bater com
shasums.arm64, o checksum arm64 é considerado aprovado - Se não bater com nenhum dos dois, ele imprime o SHA-256 de
wasm-opt_130.wasmewasm2js_130.wasme falha
- Se bater com
- Essa tarefa de CI roda tanto em hosts x86_64 quanto arm64
- A reprodutibilidade entre hosts diferentes ainda não foi alcançada, e o problema continua como um bug upstream do LLVM
- No estado atual, pelo menos dentro de cada arquitetura o build funciona de forma determinística
1 comentários
Comentários do Lobste.rs
Foi a primeira vez que descobri que o
clangexecuta secretamente owasm-optdo$PATH, e isso realmente parece absurdoPor causa disso, verifiquei se isso também afetava o
zig cc, mas felizmente isso só acontece quando oclangé usado como driver do linker, então não se aplicavaSe o
clangestiver decidindo a ordem com base na disposição dos endereços, pessoalmente eu consideraria isso um bug e, se ainda puder ser reproduzido na versão mais recente, provavelmente reportaria dessa formaJá faz anos que há esforços para eliminar esse tipo de problema
clang.exeno Windows de forma confiável como compilador cruzado é ainda mais enlouquecedorO clang parece ter umas 500 formas de presumir que vai compilar para o sistema nativo
Não é uma crítica, e eu respeito o fato de ser open source e de o OP oferecer um serviço popular gratuitamente
Ainda assim, eu realmente odeio ver a web mudando desse jeito. Está cada vez mais comum entrar num site e ser recebido por uma tela de carregamento do Anubis piscando na cara; não sei se queremos uma web em que todo site popular mostra uma tela de prova de trabalho antes de deixar você entrar
Não sei qual seria a alternativa, já que os crawlers de IA continuam chegando em massa, mas também fico em dúvida se há evidência de que prova de trabalho realmente bloqueia crawlers de IA. Eles têm financiamento enorme e já fazem muito mais computação só para ler a página, então o custo de resolver a prova de trabalho parece bem pequeno
No piloto do Anubis, foi claramente um meio eficaz de dissuasão contra tráfego indesejado, e com regras quase básicas continuou bloqueando cerca de 90% das requisições de três aplicações. No DDR foram 71,0%, no ArcLight 94,6% e no Catalog 92,4%
Em 30 de maio houve uma explosão de tráfego de bots e, até a aplicação do Anubis em 3 de junho, o Catalog ficou praticamente fora do ar. No pico de 1º de junho, chegou a 3,4 milhões de requisições HTTP vindas de 2,1 milhões de IPs únicos, com carregamento de página passando de 70 segundos. Depois da aplicação do Anubis em 4 de junho, o serviço voltou a ficar utilizável para os usuários; o total de requisições processadas pela aplicação caiu para 125 mil, e o carregamento de página melhorou para 2,12 segundos
https://lobste.rs/s/ncyfcp/anubis_pilot_project_report_june_2025
Em outro caso, o problema foi resolvido logo após implantar o Anubis, e foi possível ver o momento exato no monitoramento; depois disso, não houve mais nenhum alerta. O ataque continuava, mas a carga no servidor ficou no mínimo, e parece que o Anubis funcionou não só para bloquear scrapers de IA, mas também como proteção contra DDoS
https://lobste.rs/s/67ijih/day_anubis_saved_our_websites_from_ddos
https://orib.dev/tmp/bandwidth.png
Algumas pessoas conseguem barrá-los só com uma tag
meta refreshou um botão que precisa ser clicado. Então o Anubis funciona, mas o ponto central não é a prova de trabalho em si, e sim o comportamento inesperadoEstá ficando pior do que na época em que eu usava a web com o JavaScript desligado no navegador. Eu queria que a web continuasse sendo centrada em documentos, mas agora em todo lugar é preciso passar por Cloudflare, Anubis e portões de captcha
A ideia era que bots sempre encontram um jeito de contornar WAFs, enquanto usuários reais acabam desperdiçando ciclos de CPU na tela de carregamento
É lamentável, mas não surpreendente. Toolchains de compilador têm uma longa história de depender de pressupostos implícitos absurdos do tipo “o contexto local simplesmente precisa estar certo”
Ainda assim, é estranho ver isso no clang, porque justamente o LLVM foi um dos que mais lideraram a remoção dessas dependências. Graças a isso, por exemplo, o compilador Rust pôde existir sem um conceito separado de compilador cruzado
Isso fica evidente assim que você tenta fazer bootstrap de um sistema operacional sem depender das ferramentas de build já existentes. O processo de criar um kernel, depois uma libc e um compilador para esse kernel, executá-los e então reconstruir tudo de novo sobre o novo SO é absurdamente complexo e sensível, cheio de suposições implícitas
Como é um problema raro que afeta principalmente desenvolvedores de SO e de compiladores, quase não existem boas ferramentas nem práticas recomendadas, e para cada combinação compilador+SO deve haver umas cinco pessoas no mundo que realmente entendem o sistema inteiro
Também achava que a toolchain do Zig tirava parte dessa capacidade do LLVM, embora entenda que eles tenham feito muito trabalho para separar isso de forma mais limpa. Agora até me pergunto se eles ainda usam LLVM
Mas se o clang também sofre do mesmo problema, então talvez ele não tenha herdado do LLVM uma estrutura tão limpa assim
Pelo que sei, você usa Nix, então fiquei curioso por que não mencionou ou usou o Nix para reduzir pelo menos parte da variabilidade do ambiente
Por exemplo, algo como o problema do
wasm-optno$PATHparece o tipo de coisa que o Nix poderia mitigar. Você usou e eu deixei passar?De forma ingênua, eu achava que portar wasm para asm.js seria “fácil”, mas hoje aprendi algo novo
O título do blog parece clickbait, mas o conteúdo é bom
Eu realmente odeio clickbait