Вендоринг WASM-бинарника в Anubis: воспроизводимые сборки даются на удивление тяжело
- title
- Вендоринг WASM-бинарника в Anubis: воспроизводимые сборки даются на удивление тяжело
- type
- summary
- summary
- Три способа, которыми компилятор ломал детерминизм при вендоринге бинарника wasm2js для Anubis
- tags
- reproducible-builds, webassembly, compilers, supply-chain
- sources
- anubis-wasm-vendor-binary
- parent
- reproducible-builds
- created
- 2026-07-21
- updated
- 2026-07-21
- lang
- ru
- translation_of
- anubis-wasm-vendor-binary
- source_updated
- 2026-07-21
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Журнал сборки от Xe Iaso (xeiaso.net, 2026-06-18) о попытке собрать один небольшой завендоренный бинарник байт-в-байт идентично, и о трёх разных препятствиях со стороны тулчейна. Главная мысль: "Казалось бы, подавая на вход одни и те же байты, на выходе получишь те же самые байты. Ха. Ха-ха. А вот и нет".
Зачем нужен этот бинарник
В Anubis добавляется проверка proof-of-work на WebAssembly, чтобы администраторы могли защищать сайты с помощью челленджей без SHA256. Одно из проектных требований - логика проверки определяется один раз и используется как клиентом, так и сервером: оба подключаются к одному и тому же Wasm, поэтому работают синхронно. Но есть загвоздка: в некоторых браузерах Wasm отключён, а Iaso не хотелось отсекать таких пользователей. Решение подсмотрели в докладе The Birth and Death of JavaScript: перекомпилировать Wasm в JavaScript утилитой wasm2js из проекта binaryen - работает медленнее, зато везде.
В дистрибутивах Linux поставляются древние версии wasm2js, выдающие результат, отличный от установленной через Homebrew версии разработчика. Поэтому Anubis поставляет собственную wasm2js, собранную в Wasm с помощью wasi-sdk и закоммиченную в репозиторий. Чтобы мейнтейнеры пакетов доверяли закоммиченному бинарнику, у них должна быть возможность пересобрать его и получить ровно те же байты. А это превращает задачу в проблему воспроизводимых сборок и, как следствие, безопасности цепочки поставок.
Три источника недетерминизма
Макросы времени сборки. __DATE__ и __TIME__ вшивают астрономическое время компиляции в результат, из-за чего две сборки с разницей в пару секунд уже различаются.
Вызовы внешних утилит из $PATH в clang. clang во время сборки молча запускает wasm-opt из $PATH. На сервере aarch64 DGX Spark стоял wasm-opt v108, а на рабочей станции x86_64 - v130 из Homebrew. Старая версия спотыкалась об инструкции WebAssembly Exceptions и намертво ломала сборку. Решение: передать --no-wasm-opt на этапе линковки, чтобы убрать скрытую зависимость.
Генерация кода, зависящая от адресов в памяти. В ветке обработки исключений в clang значения сырых указателей влияли на порядок генерации блоков try_table. В итоге каждая сборка отличалась примерно на 29 байт, а индексы catch_all_ref сдвигались. Вычисления оставались идентичными, менялся только порядок байтов. Кроме того, результат различался между x86_64 и arm64, поскольку порядок обхода указателей на разных архитектурах не совпадает.
Исправления и оставшиеся проблемы
Для борьбы с кодогенерацией, зависящей от адресов, применили две меры: отключили ASLR через setarch --addr-no-randomize, чтобы указатели распределялись стабильно, и зафиксировали эталонные контрольные суммы sha256 для каждой архитектуры, добавив проверку совпадения сборки в CI на runner'ах x86_64 и arm64. Это обеспечило воспроизводимость внутри одной архитектуры. Воспроизводимость между разными архитектурами пока упирается в баг апстрима LLVM - порядок обхода указателей не инициализируется фиксированным сидом. Iaso обратил на это внимание разработчиков LLVM. Вариант "детерминировано в пределах одной архитектуры" на данный момент сочли достаточным.
См. также
- reproducible-builds - общая концепция и типичные сбои, описанные в этой статье
- dependency-vendoring - почему коммит готового бинарника требует воспроизводимости для доверия к нему
- webassembly - Wasm, Core Spec и запасной вариант с wasm2js
- wazero - среда выполнения Wasm на чистом Go, которая (как и wasmtime) требует включения поддержки исключений флагом
- reuse-less-software - более широкий контекст темы вендоринга, куда укладывается этот случай
- supply-chain-security - цепочка доверия, которую защищает верифицируемый закоммиченный бинарник