SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-21

Что на самом деле доказывают воспроизводимые сборки

Контрольная сумма лишь подтверждает целостность файла при передаче. Воспроизводимая сборка доказывает соответствие бинарного кода исходникам, исключая внедрение бэкдоров при компиляции.

Что доказывают воспроизводимые сборки

Воспроизводимые сборки доказывают один узкий факт: полученный вами бинарный файл был создан именно из этого исходного кода. Любой желающий может взять те же исходники, выполнить сборку повторно и сравнить байты. Проверка перестает быть задачей, доступной только издателю.

Проект Reproducible Builds определяет это следующим образом: «Сборка является воспроизводимой, если при наличии того же исходного кода, среды сборки и инструкций по сборке любая сторона может воссоздать побитово идентичные копии всех указанных артефактов». Само сравнение выполняется с помощью хеша. Вся сложность заключается в том, чтобы зафиксировать среду и инструкции достаточно строго, чтобы два разных компьютера выдали одинаковый результат.

Почему контрольная сумма не дает ответа на этот вопрос

Опубликованная контрольная сумма подтверждает, что файл был доставлен без повреждений. Цифровая подпись этой контрольной суммы подтверждает, что файл поступил от владельца ключа. Ни то, ни другое не дает информации о том, что происходило до создания артефакта. Если сборочный сервер издателя скомпрометирован, вредоносный бинарный файл будет иметь контрольную сумму и подпись, идентичные чистому файлу, поэтому все проверки на стороне получателя пройдут успешно. То же самое происходит, если сопровождающий выполняет сборку из рабочего дерева, которое никогда не было отправлено в репозиторий.

В этом заключается разрыв. Вы можете изучить исходный код, проверить подпись и контрольную сумму, но при этом запускать код, которого никогда не было в репозитории. Таким образом, воспроизводимость — это тема, отличная от проверки загруженного файла по опубликованной контрольной сумме. Контрольная сумма защищает процесс передачи. Повторная сборка защищает всё, что происходило до передачи.

Эта атака не является теоретической. Компрометация SolarWinds Orion в 2020 году произошла именно таким образом: система сборки создавала подписанные артефакты, которые не соответствовали исходному коду, прошедшему проверку. Все проверки подписей проходили успешно, так как подписи проверяют только сам артефакт.

Что не доказывает воспроизводимая сборка

Это аспект, который часто переоценивают, поэтому будьте точны в отношении ограничений.

  • Она не гарантирует безопасность исходного кода. Бэкдор, добавленный в открытый код, будет собираться воспроизводимо, и каждый, кто выполнит повторную сборку, подтвердит это, так как все они соберут один и тот же вредоносный код. Воспроизводимость переносит фокус внимания на дерево исходного кода. Кто-то всё равно должен его прочитать, поэтому политика проверки, включая политики использования ИИ-помощников в проектах с открытым кодом, остаётся отдельной мерой контроля.
  • Она не гарантирует безопасность входных данных. Зависимости являются частью того, что вы собираете. Вредоносный пакет, разрешённый во время сборки, компилируется в артефакт, и каждый, кто выполнит повторную сборку с той же зависимостью, подтвердит результат. Именно так атака на цепочку поставок npm достигает сервера, а воспроизводимая сборка точно её повторяет.
  • Она не гарантирует честность инструментария. Если компилятор скомпрометирован, каждый, кто выполнит повторную сборку с его помощью, получит одинаковый скомпрометированный результат, и все вердикты совпадут. Воспроизводимость повышает стоимость такой атаки, но не обнаруживает её.
  • Она ничего не говорит об уязвимостях. Старая библиотека, воспроизведённая побитово, остаётся старой библиотекой с известными изъянами, поэтому продолжайте проверку сервера на наличие известных CVE по собственному графику.

Воспроизводимость устраняет одну конкретную позицию атакующего: сборочный сервер и весь путь от исходного кода до бинарного файла. Пока пакет не является воспроизводимым, никто, кроме издателя, не может проверить этот путь в принципе.

Почему один и тот же исходный код дает разные байты

Большинство программ по умолчанию не являются воспроизводимыми, и причины этого довольно прозаичны. Компиляторы и форматы архивов записывают сведения о машине, на которой они были запущены.

  • Временная метка. Форматы tar, ar и zip сохраняют время изменения файлов, поэтому сборка в разное время приводит к изменению выходного файла.
  • Путь. Отладочная информация записывает абсолютный путь к директории сборки, поэтому сборка в /home/alice/src и сборка в /build/pkg будут различаться, даже если код идентичен.
  • Порядок. Чтение директории возвращает записи в порядке файловой системы, поэтому порядок следования объектов в строке линковки или элементов в архиве может меняться на разных машинах.
  • Идентификация. Скрипты сборки встраивают имя пользователя, имя хоста или локаль того, кто выполнял сборку.
  • Решения, принятые во время сборки. Определение возможностей процессора или использование случайных чисел делает результат зависимым от машины, а не от исходного кода.

Вы можете увидеть проявление первой причины примерно за десять секунд:

mkdir -p /tmp/rb && cd /tmp/rb
echo hello > a.txt && tar cf one.tar a.txt
sleep 2
echo hello > a.txt && tar cf two.tar a.txt
sha256sum one.tar two.tar

Два хеша различаются, потому что заголовок tar сохраняет время изменения a.txt, а перезапись файла сдвинула это время на две секунды вперед. Содержимое при этом идентично байт в байт. Фиксация метаданных решает эту проблему:

export SOURCE_DATE_EPOCH=1700000000
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf three.tar a.txt
touch a.txt
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf four.tar a.txt
sha256sum three.tar four.tar

Теперь хеши совпадают, так как ни одно поле в заголовке архива не зависит от текущего состояния машины. --sort=name фиксирует порядок, --mtime фиксирует время, а флаги владения предотвращают запись вашего идентификатора пользователя.

Анализ различий с помощью diffoscope

Когда две сборки различаются, sha256sum сообщает только о самом факте наличия различий. Утилита diffoscope создана для того, чтобы объяснить причины в понятном для человека виде. Она рекурсивно распаковывает обе стороны, преобразует бинарные форматы в текстовые и сравнивает полученный текст. Она поддерживает пакеты Debian, ELF-бинарные файлы, архивы tar и ZIP, PDF-документы, базы данных SQLite и более сотни других форматов.

sudo apt install -y diffoscope
diffoscope one.tar two.tar

Для пары tar-архивов из примера выше отчет будет коротким. В сокращенном виде он выглядит так:

--- one.tar
+++ two.tar
├── file list
│ @@ -1 +1 @@
│ -rw-r--r-- 0/0  6 2026-08-18 10:14:02.000000 a.txt
│ +rw-r--r-- 0/0  6 2026-08-18 10:14:04.000000 a.txt

Это и есть полный диагноз: одинаковый размер, одинаковый путь, одинаковые права доступа, разное время модификации. Реальный пакет создает гораздо более длинный отчет, поэтому сохраните его в файл и откройте в браузере:

diffoscope --html report.html build1.changes build2.changes

diffoscope завершается с кодом 0, если входные данные идентичны, с кодом 1 при наличии различий и с кодом 2 при возникновении ошибок, поэтому его можно сразу использовать в CI-задачах без дополнительных скриптов-оберток. На небольшом VPS установите diffoscope-minimal вместо diffoscope: полная версия пакета тянет за собой большое количество вспомогательных инструментов для различных форматов, которые вам, скорее всего, никогда не понадобятся.

Что исправляет SOURCE_DATE_EPOCH и где его действие ограничено

SOURCE_DATE_EPOCH — это переменная окружения, содержащая одно число: время последнего изменения исходного кода в секундах, прошедших с 1 января 1970 года по UTC. Инструмент сборки, поддерживающий эту переменную, использует данное число везде, где в противном случае он запрашивал бы текущее время у операционной системы. Устанавливайте это значение на основе системы контроля версий, чтобы оно следовало за исходным кодом, а не за процессом сборки:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)

В пакетах Debian утилита debhelper автоматически экспортирует это значение из файла changelog. Ручная установка в debian/rules выглядит следующим образом:

export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog -STimestamp)

Поддержка этой переменной реализована на уровне отдельных инструментов, а не глобально. Её учитывают cmake версии 3.8 и новее, gcc версии 7 и новее, rpm версии выше 4.13 и Docker buildx версии 0.10 и новее. Ваши собственные скрипты не будут её использовать, пока вы не добавите соответствующую логику. Если скрипт вызывает date, передайте ему эту переменную:

BUILD_DATE="$(date --utc --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d)"

При реализации важно соблюдать одно правило. Если переменная уже установлена, это значение и есть текущее время для вашей сборки, поэтому никогда не перезаписывайте то, что предоставил вызывающий процесс.

Контейнерные образы сталкиваются с той же проблемой в другой обёртке. Docker buildx версии 0.10 и новее передаёт SOURCE_DATE_EPOCH из вашей оболочки в процесс сборки в качестве аргумента. Чтобы временные метки файлов внутри слоёв были корректными, экспортёр должен их перезаписать; эта возможность была добавлена в BuildKit версии 0.13, а документированный способ отправляет результат в реестр:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
docker buildx build --output type=image,name=registry.example.com/app:1.0,push=true,rewrite-timestamp=true .

Тестирование собственной сборки с помощью reprotest

reprotest выполняет сборку из одного и того же исходного кода дважды, намеренно изменяя среду между запусками, а затем сравнивает результаты. Вариативность — ключевая особенность этого процесса. По умолчанию утилита варьирует путь сборки, время, часовой пояс, локаль, umask, имя хоста, пользователя и группу, количество CPU, домашний каталог и порядок следования файлов.

sudo apt install -y reprotest
reprotest . -- null

Все аргументы после -- определяют бэкенд среды сборки, а null указывает на текущую систему. Добавьте -vv -d, чтобы сохранить временные каталоги для анализа, как показано в reprotest . -vv -- null -d. Используйте reprotest auto -- null, чтобы позволить утилите самостоятельно определить тип дерева исходного кода.

Некоторые вариации требуют привилегий или дополнительных пакетов; при невозможности их выполнения утилита выдает ошибку. Отключите такие вариации вместо запуска всей процедуры от имени root:

reprotest --vary=-user_group,-domain_host,-fileordering auto -- null

Любое несоответствие, выявленное reprotest, — это проблема, о которой позже публично сообщит система пересборки, указав название вашего проекта.

Что означает вердикт системы пересборки

Система пересборки (rebuilder) — это машина, не принадлежащая издателю пакета. Она берет опубликованный исходный код и зафиксированное окружение сборки, заново собирает пакет и сравнивает полученный результат с артефактом в архиве. Вердикт имеет ценность только потому, что машина является независимой.

Debian фиксирует окружение в файле .buildinfo, который dpkg-buildpackage записывает рядом с .deb. Наиболее важная часть — это поля. Installed-Build-Depends содержит список всех установленных пакетов, которые могли повлиять на сборку, с указанием точных версий. Build-Path фиксирует место, где выполнялась сборка. Environment записывает значимые переменные окружения. Checksums-Sha256 описывает выходные данные. Этот файл служит инструкцией для повторной попытки:

sudo apt install -y devscripts mmdebstrap
debrebuild --buildresult=./artifacts --builder=mmdebstrap hello_2.10-2_amd64.buildinfo

debrebuild считывает файл buildinfo и загружает точные версии зависимостей из snapshot.debian.org, поэтому пересборка сегодня может использовать те версии пакетов, которые существовали в день исходной сборки. Сборщику mmdebstrap не требуется настройка chroot и права суперпользователя. Сравните полученные артефакты с копией из архива с помощью diffoscope.

Arch Linux использует rebuilderd, который выполняет эту задачу непрерывно и публикует вердикты:

rebuildctl -H https://reproducible.archlinux.org pkgs ls --name rebuilderd

Статусы делятся на GOOD, BAD и UNKWN, и прямое толкование каждого из них может быть ошибочным. GOOD означает, что независимая сторона получила идентичные байты; это сильное утверждение о процессе сборки, но оно ничего не говорит об исходном коде. BAD почти никогда не означает атаку: обычно причина кроется в метке времени или пути, которые не были зафиксированы при упаковке, поэтому rebuilderd может приложить отчет diffoscope к результату сбоя. UNKWN означает, что пакет никто не проверял, а непроверенный пакет не является успешно прошедшим проверку.

Таким образом, операционное правило простое. Вердикт BAD — это повод изучить отчет. Если в отчете видны метки времени, пути сборки или порядок элементов, создайте баг-репорт на упаковку. Если же отчет показывает различия в исполняемом коде без подобных объяснений, прекратите развертывание этой сборки и эскалируйте проблему.

Насколько воспроизводим Debian в данный момент?

ChartDebian packages reproducible in the test framework, amd64
The data behind this chart
[
  {
    "label": "unstable",
    "percent_reproducible": 94.2,
    "tested_count": "41,163"
  },
  {
    "label": "forky",
    "percent_reproducible": 93.4,
    "tested_count": "39,059"
  },
  {
    "label": "experimental",
    "percent_reproducible": 67.0,
    "tested_count": "588"
  }
]

На день написания этой статьи уровень воспроизводимости пакетов в ветке unstable для архитектуры amd64 составлял 94.2% из 41,163 протестированных пакетов. В ветке experimental этот показатель составлял 67.0% для гораздо меньшей и более новой выборки из 588 пакетов, что ожидаемо для ПО, исправление которого ещё не завершено.

Эти данные взяты со страницы Debian на сайте tests.reproducible-builds.org по состоянию на 2026-08-18, когда на странице стояла отметка «Last update: 2026-08-18 16:02 UTC». Эти показатели меняются. Читайте данные трекера, а не цитируйте этот абзац через полгода.

Важнее самого процента один нюанс. Данная инфраструктура собирает каждый пакет дважды на собственном оборудовании, изменяя среду между сборками, и сравнивает полученные результаты. Это позволяет измерить, может ли пакет собираться воспроизводимо. Это не проверка того, соответствует ли .deb, находящийся в архиве, исходному коду; это отдельная задача пересборщика (rebuilder), сравнивающего результат с опубликованным артефактом. Оба показателя полезны. Они отвечают на разные вопросы, однако люди часто цитируют первый так, будто он является вторым.

Что делать на собственном сервере

Вы не собираетесь пересобирать дистрибутив. Части, которые переносятся на обычный сервер, меньше и проще.

  • Фиксируйте инструментарий. Базовый образ, на который ссылается тег, может измениться без предупреждения. Ссылайтесь на него по дайджесту и записывайте этот дайджест вместе с релизом.
  • Записывайте входные данные. Храните lock-файл, дайджест образа и версию компилятора рядом с артефактом. Сборку, среду которой невозможно восстановить, нельзя пересобрать, а значит, её невозможно проверить.
  • Выполняйте сборку в CI дважды и прерывайте задачу, если результаты различаются. Это стоит одной дополнительной сборки и позволяет обнаружить недетерминизм в тот же день, когда его кто-то внёс, а не год спустя во время инцидента.
  • Удаляйте пути, которые встраивает компилятор. Для Go флаг go build -trimpath -buildvcs=false удаляет директорию сборки и метку системы контроля версий, а go version -m ./app выводит то, что в итоге попало в бинарный файл.
  • Храните хеш того, что вы развернули. Когда нужно узнать, соответствует ли запущенный бинарный файл версии исходного кода, эта запись — единственное, что может дать ответ.

Проверка в CI состоит из четырех строк:

set -eu
./build.sh && mv dist/app app.1
./build.sh && mv dist/app app.2
diffoscope --text - app.1 app.2

diffoscope завершается с ненулевым кодом, если два артефакта различаются, поэтому задача прерывается сама по себе и оставляет в логе понятное объяснение. В этом и заключается вся идея, масштабированная до одного репозитория: утверждение о том, что бинарный файл получен из дерева исходного кода, должно быть чем-то, что может проверить вторая машина.

FAQ

Означает ли воспроизводимая сборка, что программное обеспечение безопасно?

Нет. Это лишь доказывает, что бинарный файл соответствует исходному коду, и ничего более. Бэкдор, добавленный в публичный репозиторий, будет собираться воспроизводимо, и каждый, кто повторит сборку, подтвердит это, так как все они собрали один и тот же вредоносный код. Пакет с известной уязвимостью CVE собирается идеально и остаётся уязвимым. Воспроизводимость устраняет одну позицию атакующего — сборочный сервер и путь от исходного кода до бинарного файла. Анализ исходного кода и отслеживание уязвимостей — это отдельные задачи, которые воспроизводимость за вас не выполняет.

Почему мои две сборки различаются, если в исходном коде ничего не менялось?

Почти всегда причина в метке времени, пути или порядке следования. Форматы архивов, такие как tar и zip, сохраняют время изменения файлов, поэтому выгрузка кода, сделанная в разное время, приводит к получению разных байтов. Отладочная информация записывает абсолютный путь к директории сборки, поэтому /home/alice/src и /build/pkg создают разные бинарные файлы из идентичного кода. Чтение директорий возвращает записи в порядке файловой системы, поэтому объектные файлы при компоновке могут быть упорядочены по-разному на разных машинах. Запустите diffoscope build1 build2, и отчет укажет, в чем именно причина, вместо того чтобы гадать.

Что такое SOURCE_DATE_EPOCH и нужно ли его устанавливать?

Это стандартная переменная окружения, содержащая одно число: время последнего изменения исходного кода в секундах, прошедших с 1 января 1970 года UTC. Инструменты, поддерживающие эту переменную, используют данное значение везде, где они иначе обращались бы к системным часам. Установите её из системы контроля версий с помощью export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct). Это не происходит автоматически и не является универсальным решением. Её учитывают только те инструменты, в которых она реализована, поэтому ваши собственные скрипты сборки должны считывать её самостоятельно. Скрипт, который вызывает date, будет продолжать проставлять текущее время, пока вы это не измените.

Что делать, если система пересборки сообщает BAD?

Прежде чем предпринимать что-либо, прочитайте отчет. Вердикт BAD означает, что одна из независимых сборок не дала идентичных байтов, и обычно причина кроется в недетерминизме процесса упаковки, а не в атаке. rebuilderd может создать отчет diffoscope именно для таких случаев. Если различия заключаются в метках времени, путях сборки или порядке файлов, это ошибка упаковки, о которой стоит сообщить. Если же различие касается исполняемого кода и не имеет такого объяснения, прекратите развертывание этой сборки, сохраните артефакты и передайте информацию издателю.

#reproducible-builds#supply-chain#diffoscope#debian#verification