Je n’aime pas les compilateurs
(xeiaso.net)- Anubis étend la preuve de travail de protection des sites web au-delà de SHA-256 et est en train de concevoir un système où le client et le serveur exécutent la même logique de vérification WebAssembly
- Pour ne pas exclure les environnements où WebAssembly est désactivé, un chemin de recompilation vers JavaScript a été prévu, mais il est plus lent que WebAssembly et peut l’être encore davantage si le JIT est aussi désactivé
- La version Linux de
wasm2jsdans les distributions était obsolète et produisait une sortie différente de celle de Homebrew, ce qui a conduit à embarquerwasm2jscompilé avec wasi-sdk pour obtenir des builds reproductibles - Dans les builds C/C++,
__DATE__,__TIME__, lewasm-opttrouvé via$PATHet l’ordre des pointeurs dans le code de gestion des exceptions peuvent faire varier la sortie octet par octet même avec les mêmes entrées - L’implémentation finale obtient une déterminisme intra-architecture grâce à
--no-wasm-opt,setarch --addr-no-randomize, une vérification SHA-256 distincte pour x86_64 et arm64, et une validation de rebuild en CI
Preuve de travail WebAssembly d’Anubis et chemin de repli JavaScript
- Anubis veut ajouter une vérification de proof-of-work basée sur WebAssembly afin que les administrateurs puissent utiliser pour la protection de leur site web un mécanisme de preuve de travail autre que SHA-256
- L’objectif principal est de ne pas implémenter séparément la logique de vérification côté client et côté serveur, mais de la définir à un seul endroit
- Le client et le serveur se branchent sur le même WebAssembly pour exécuter la logique de vérification
- L’architecture vise à vérifier que les deux côtés fonctionnent en lockstep
- Les clients avec WebAssembly désactivé restent aussi dans le périmètre
- Il y a la contrainte de ne pas vouloir exclure de fait les utilisateurs du site web
- Anubis doit trouver un équilibre entre l’expérience utilisateur, l’expérience administrateur et l’expérience développeur
- Le contournement retenu consiste à recompiler WebAssembly en JavaScript
- L’idée s’inspire de The Birth and Death of JavaScript
- Le JavaScript obtenu est plus lent qu’un WebAssembly équivalent
- Désactiver WebAssembly peut aussi désactiver le JIT JavaScript, ce qui peut encore ralentir l’exécution
- Il faut encore étudier si cette approche est plus efficace que le JavaScript existant sur du matériel peu puissant
Pourquoi il a fallu embarquer wasm2js
- L’outil nécessaire est
wasm2jsdu projet binaryen wasm2jsexiste sous forme de paquet dans les distributions Linux, mais la version fournie par les distributions était trop ancienne et ne produisait pas la même sortie que la version Homebrew utilisée en développement- Les builds reproductibles exigent un caractère déterministe de la sortie
- Pour que les utilisateurs et les packageurs puissent faire confiance au binaire
wasm2jscommité dans le dépôt Anubis, il faut pouvoir reconstruire la même version soi-même et obtenir exactement les mêmes octets - Idéalement, il faudrait aussi obtenir les mêmes octets sur la machine d’une autre personne
- Pour que les utilisateurs et les packageurs puissent faire confiance au binaire
- Pour cela, une copie de
wasm2jscompilée avec wasi-sdk pour la cible WebAssembly est incluse
Là où la reproductibilité se casse facilement dans les builds C/C++
- Même avec les mêmes octets source et les mêmes entrées, la sortie du compilateur n’est pas forcément toujours identique octet pour octet
- En C/C++, de simples macros intégrées comme
__DATE__et__TIME__suffisent à produire une sortie non déterministe- L’exemple
hello.cppest écrit pour afficher la date et l’heure du build - Un build a affiché
Jun 18 2026 00:00:59, un autreJun 18 2026 00:01:11 - Le code source avait les mêmes octets, mais la sortie du compilateur était différente
- L’exemple
- Un petit compilateur pourrait en théorie être déterministe, mais en pratique les compilateurs réels comportent beaucoup plus de variables complexes
Le problème de Clang qui exécutait silencieusement wasm-opt trouvé dans $PATH
- En plus de
wasm2js, binaryen fournitwasm-opt, qui optimise la sortie des compilateurs WebAssembly - Clang exécute
wasm-optvia un appel externe pendant le build- En général, c’est un comportement raisonnable pour améliorer les performances
- Ici, la différence de version du
wasm-optprésent dans$PATHa cassé la reproductibilité
- Sur la DGX Spark,
wasm-optétait/usr/bin/wasm-opten version 108, tandis que sur la workstation la version Homebrew dewasm-optétait la version 130 - wasi-sdk et binaryen dépendent de la WebAssembly Exceptions extension
- Selon Can I use, 93,86 % des utilisateurs de navigateurs utilisent un moteur prenant cela en charge
- Le C++ utilise beaucoup les exceptions, et la gestion native des exceptions en WebAssembly peut réduire le boilerplate
- Avec wasmtime et wazero, il faut activer explicitement la prise en charge des exceptions
- On peut passer
-W exceptions=yà wasmtime - wazero nécessite un harnais d’exécution personnalisé
- On peut passer
- Sur les machines arm, un ancien
wasm-opts’arrêtait lorsqu’il rencontrait des instructions de gestion des exceptions, ce qui faisait échouer le build - Le passage de
--no-wasm-optà l’étape d’édition de liens supprime ce chemin de non-reproductibilité
L’impact de l’agencement des adresses sur la génération du code de gestion des exceptions
- La version de Clang utilisée montrait une génération de code sensible aux adresses dans le chemin de gestion des exceptions lors de la compilation de
wasm2js - Les valeurs de pointeurs brutes influençaient l’ordre de sortie de certains blocs
try_table- Cela provoquait environ 29 octets de différence à chaque build
- Les calculs restaient presque identiques, mais l’ordre des octets changeait et les références
catchaussi
- Même en compilant la même version figée de
wasm2jssur une machine arm64, l’ordre d’itération des pointeurs différait de celui de la workstation, provoquant le même problème - Deux contournements ont été mis en place
setarch --addr-no-randomizedésactive la randomisation de l’espace d’adressage pour ce build- Des checksums SHA-256 de référence pour x86_64 et arm64 sont générés sur des machines de confiance
- La CI exécute
./build.shdepuis./utils/wasm/wasm2js, puis vérifie les checksums- Si cela correspond à
shasums.x86_64, le checksum x86_64 est validé - Si cela correspond à
shasums.arm64, le checksum arm64 est validé - Si rien ne correspond, la CI affiche les SHA-256 de
wasm-opt_130.wasmetwasm2js_130.wasmpuis échoue
- Si cela correspond à
- Cette tâche CI tourne à la fois sur des hôtes x86_64 et arm64
- La reproductibilité sur l’ensemble des hôtes n’est pas encore acquise, et le problème reste un bug LLVM upstream
- Dans l’état actuel, au moins à l’intérieur d’une même architecture, le build fonctionne de manière déterministe
1 commentaires
Avis sur Lobste.rs
C’est la première fois que j’apprends que
clangexécute discrètementwasm-optdepuis le$PATH, et ça me paraît complètement absurdeJ’ai vérifié si cela affectait aussi
zig cc, mais heureusement ce n’est exécuté que lorsqu’on utiliseclangcomme pilote de l’éditeur de liens, donc ce n’était pas concernéSi
clangdétermine l’ordre en dépendant de la disposition des adresses, personnellement je considérerais ça comme un bug, et si c’est toujours reproductible dans la dernière version, je le signalerais comme telDes efforts sont en cours depuis des années pour éliminer ce genre de problème
clang.exede façon fiable comme compilateur croisé sous Windows rendrait encore plus fouclang suppose d’environ 500 façons qu’il va construire pour le système natif
Ce n’est pas pour critiquer, et je respecte le fait que ce soit open source et que l’OP fournisse gratuitement un service populaire
Mais je déteste vraiment voir le web évoluer ainsi. Il devient courant d’arriver sur un site et de voir surgir un écran de chargement Anubis ; je ne sais pas si on veut vraiment d’un web où chaque site populaire affiche un écran de preuve de travail
Je ne sais pas non plus quelle est l’alternative avec l’afflux permanent de crawlers IA, mais je me demande aussi s’il existe des preuves que la preuve de travail bloque réellement les crawlers IA. Ils disposent de financements énormes et effectuent déjà bien plus de calcul pour lire les pages, donc le coût de résolution de la preuve de travail semble minime
Dans le pilote Anubis, cela a clairement été un moyen de dissuasion efficace contre le trafic indésirable, et avec des règles proches du minimum, environ 90 % des requêtes vers trois applications ont continué à être bloquées. DDR était à 71,0 %, ArcLight à 94,6 % et Catalog à 92,4 %
Le 30 mai, le trafic de bots a explosé, rendant le catalogue pratiquement indisponible jusqu’au déploiement d’Anubis le 3 juin ; au pic du 1er juin, on a atteint 3,4 millions de requêtes HTTP depuis 2,1 millions d’IP uniques, avec des temps de chargement dépassant 70 secondes. Après le déploiement d’Anubis le 4 juin, le service est redevenu accessible aux utilisateurs ; le nombre total de requêtes traitées par l’application est tombé à 125 000 et le temps de chargement des pages s’est amélioré à 2,12 secondes
https://lobste.rs/s/ncyfcp/anubis_pilot_project_report_june_2025
Dans un autre cas, le problème a été résolu immédiatement après le déploiement d’Anubis ; le moment exact était visible dans la supervision, et il n’y a ensuite eu plus aucune alerte. L’attaque continuait, mais la charge serveur était au plus bas ; Anubis semble donc avoir servi non seulement à bloquer les scrapers IA, mais aussi de protection DDoS
https://lobste.rs/s/67ijih/day_anubis_saved_our_websites_from_ddos
https://orib.dev/tmp/bandwidth.png
Certaines personnes les bloquent juste avec une balise
meta refreshou un bouton sur lequel il faut cliquer. Donc Anubis fonctionne, mais l’essentiel n’est pas la preuve de travail en elle-même : c’est le comportement inattenduC’est devenu encore plus pénible que lorsqu’on utilisait le web avec un navigateur JavaScript désactivé. J’aimerais que le web reste simplement centré sur les documents, mais maintenant il faut partout passer par Cloudflare, Anubis et des barrières de CAPTCHA
Les bots finissent toujours par trouver un moyen de contourner les WAF, tandis que les vrais utilisateurs gaspillent des cycles CPU sur un écran de chargement
C’est regrettable, mais pas surprenant. Les toolchains de compilation ont une très longue histoire de dépendances implicites absurdes du genre « le contexte local doit simplement être correct »
Cela dit, LLVM fait partie de ceux qui ont mené la charge pour éliminer ce type de dépendances, donc voir ça dans clang est assez étrange. C’est d’ailleurs ce qui a permis, par exemple, au compilateur Rust d’exister sans notion distincte de compilateur croisé
Cela saute immédiatement aux yeux quand on essaie de bootstrapper un OS sans s’appuyer sur les outils de build existants. Construire un noyau, puis une libc et un compilateur pour ce noyau, les exécuter, puis tout reconstruire sur le nouvel OS est un processus ridiculement complexe et fragile, rempli d’hypothèses implicites
Comme c’est un problème rare qui ne concerne surtout que les développeurs d’OS et de compilateurs, il existe très peu de bons outils ou de bonnes pratiques, et pour chaque combinaison compilateur+OS il doit y avoir à peine cinq personnes dans le monde qui comprennent réellement l’ensemble
Je pensais aussi que la toolchain Zig tirait une partie de cette capacité de LLVM, même si je comprends bien sûr qu’ils ont beaucoup travaillé pour séparer tout cela plus proprement. Je me demande même s’ils utilisent encore LLVM aujourd’hui
Mais si clang a les mêmes problèmes, alors j’ai l’impression que LLVM n’a peut-être pas transmis une architecture aussi propre que je le pensais
Il me semblait que tu utilisais Nix ; je me demande pourquoi tu n’as pas mentionné ou utilisé Nix pour au moins réduire une partie de la variabilité de l’environnement
Par exemple, pour un problème comme
wasm-optdans le$PATH, Nix aurait sans doute pu l’atténuer ; ou bien tu l’as utilisé et je l’ai raté ?J’imaginais naïvement que convertir du wasm vers asm.js serait « facile », mais aujourd’hui j’ai appris quelque chose
Le titre du blog ressemble à du clickbait, mais le contenu est bon
Je déteste vraiment le clickbait