1 pontos por GN⁺ 11 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • 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 wasm2js das 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 um wasm2js compilado com o wasi-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__, do wasm-opt no $PATH e 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 wasm2js do projeto binaryen
  • O wasm2js existe 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 wasm2js versionado 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 isso, foi incluída uma cópia do wasm2js compilada 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.cpp foi escrito para imprimir a data e a hora do build
    • Um build imprimiu Jun 18 2026 00:00:59, e outro imprimiu Jun 18 2026 00:01:11
    • Os bytes do código-fonte eram os mesmos, mas a saída do compilador foi diferente
  • 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 o wasm-opt, que otimiza a saída de compiladores WebAssembly
  • Durante o build, o Clang chama o wasm-opt via shell out
    • Em geral, esse é um comportamento razoável para melhorar desempenho
    • Aqui, porém, a diferença de versão do wasm-opt presente no $PATH quebrou a reprodutibilidade
  • O wasm-opt do DGX Spark era a versão 108 em /usr/bin/wasm-opt, enquanto o wasm-opt do Homebrew na workstation era a versão 130
  • O wasi-sdk e 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
  • Em uma máquina arm, o wasm-opt antigo encontrava instruções de exceção e encerrava, fazendo o build falhar
  • Ao passar --no-wasm-opt na 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 catch também
  • Mesmo compilando a mesma versão fixa do wasm2js em 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
  • O CI executa ./build.sh em ./utils/wasm/wasm2js e 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.wasm e wasm2js_130.wasm e falha
  • 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 clang executa secretamente o wasm-opt do $PATH, e isso realmente parece absurdo
    Por causa disso, verifiquei se isso também afetava o zig cc, mas felizmente isso só acontece quando o clang é usado como driver do linker, então não se aplicava
    Se o clang estiver 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 forma

    • Xe disse que reportaria isso upstream em outro lugar, e isso é claramente um bug de determinismo do LLVM
      Já faz anos que há esforços para eliminar esse tipo de problema
    • Tentar usar clang.exe no Windows de forma confiável como compilador cruzado é ainda mais enlouquecedor
      O 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

    • Há evidências de que prova de trabalho bloqueia crawlers de IA. Já apareceram vários posts sobre isso aqui
      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
    • Não precisa necessariamente ser prova de trabalho. Implantei um bloqueador stateless sem JS em https://shithub.us, e provavelmente custa para os scrapers contornarem isso pelo menos tanto quanto custa uma simples prova de trabalho
      https://orib.dev/tmp/bandwidth.png
    • Ninguém sabe exatamente o que são esses crawlers nem para quê servem, mas muitos parecem bastante preguiçosos e não lidam bem com tratamentos incomuns
      Algumas pessoas conseguem barrá-los só com uma tag meta refresh ou 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 inesperado
    • Com certeza eu não quero essa web
      Está 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
    • Desde o começo já se sabia que qualquer APT poderia acelerar o cálculo da resposta do Anubis. Isso já estava escrito até na prova de conceito do ano passado
      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

    • Fiquei surpreso ao ouvir isso. Eu achava que o LLVM, ao contrário do GCC, tinha sido projetado com compilação cruzada em mente, abstraindo host e alvo
      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-opt no $PATH parece 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

    • Eu também achava isso. Infelizmente, na prática é muito mais complicado do que parece
  • O título do blog parece clickbait, mas o conteúdo é bom
    Eu realmente odeio clickbait