EnglishРусский Map
Reproducible Builds

Вендоринг WASM-бинарника в Anubis: воспроизводимые сборки даются на удивление тяжело

title
Вендоринг WASM-бинарника в Anubis: воспроизводимые сборки даются на удивление тяжело
type
summary
summary
Три способа, которыми компилятор ломал детерминизм при вендоринге бинарника wasm2js для Anubis
tags
reproducible-builds, webassembly, compilers, supply-chain
created
2026-07-21
updated
2026-07-21
lang
ru
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 - цепочка доверия, которую защищает верифицируемый закоммиченный бинарник