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

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

4 августа 2025 г.

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

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

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

GO 1.21.0 - это первый набор инструментов GO с идеально воспроизводимыми сборками. Более ранние инструменты были возможны для воспроизведения, но только при значительных усилиях, и, вероятно, никто не сделал: они просто доверяли, что двоичные файлы размещены наgo.dev/dlбыли правильные. Теперь легко «доверять, но проверить».

В этом посте объясняется, что входит в создание сборки, воспроизводимых, исследует множество изменений, которые мы должны были внести, чтобы сделать воспроизводимые инструменты GO, а затем демонстрирует одно из преимуществ воспроизводимости, проверяя пакет Ubuntu для GO 1.21.0.

Сделать сборку

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

Наиболее распространенным непреднамеренным входом в системы сборки является текущее время. Если сборка записывает исполняемый файл на диск, файловая система записывает текущее время как время модификации исполняемого файла. Если сборка, то пакеты, которые файл, используя инструмент, такой как «TAR» или «ZIP», время изменения записывается в архив. Мы, конечно, не хотели, чтобы наша сборка изменилась в зависимости от текущего времени, но это так. Таким образом, текущее время оказывается непреднамеренным вводом для сборки. Хуже того, большинство программ не позволяют вам предоставлять текущее время в качестве ввода, поэтому нет способа повторить эту сборку. Чтобы исправить это, мы могли бы установить марки времени на созданных файлах в Unix Time 0 или в определенное время, чтение из одного из исходных файлов сборки. Таким образом, текущее время больше не является соответствующим вводом для сборки.

Общие соответствующие входы на сборку включают:

  • конкретная версия исходного кода для построения;
  • конкретные версии зависимостей, которые будут включены в сборку;
  • операционная система, управляющая сборкой, которая может повлиять на имена путей в полученных двоичных файлах;
  • Архитектура ЦП в системе сборки, которая может повлиять на то оптимизации, которые компилятор использует, или на макет определенных структур данных;
  • используется версия компилятора, а также варианты компилятора передаются в нее, которые влияют на то, как компилируется код;
  • Имя каталога, содержащего исходный код, который может отображаться в информации отладки;
  • Имя пользователя, имя группы, UID и GID учетной записи, запускающей сборку, которая может отображаться в метаданных файлах в архиве;
  • и многое другое.

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

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

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

Идеально воспроизводимые сборки для Go

По состоянию на GO 1.21, инструмент GO идеально воспроизводим: его единственным соответствующим входом является исходный код для этой сборки. Мы можем построить конкретный инструмент (скажем, перейти на Linux/x86-64) на хосте Linux/x86-64, или на хосте Windows/ARM64, или на хосте FreeBSD/386 или любой другой хост, который поддерживает GO, и мы можем использовать любой компилятор GO Bootstrap, включая загрузку на все пути назад до Go 1.4, и мы можем предоставить любую другую информацию. Ничто из этого не меняет набор инструментов, которые создаются. Если мы начнем с одного и того же исходного кода инструмента, мы выберем те же самые двоичные файлы инструментов.

Эта идеальная воспроизводимость является кульминацией усилий, датируемых первоначально, чтобы пройти 1,10, хотя большая часть усилий была сконцентрирована в GO 1,20 и Go 1,21. В этом разделе выделены некоторые из наиболее интересных соответствующих входов, которые мы устранили.

Reproducibility in Go 1.10

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

Мы начнем с создания источников для текущего инструмента GO, используя более раннюю версию GO, Bootstrap Toolchain (GO 1.10 Используется GO 1.4, написанный в C; GO 1.21 Использование GO 1.17). Это производит «Toolchain1», который мы используем для снова построить все, производя «ToolChain2», который мы используем для снова построить все, производя «ToolChain3».

Toolchain1 и Toolchain2 были построены из тех же источников, но с различными реализациями GO (компиляторы и библиотеки), поэтому их двоичные файлы наверняка будут разными. Однако, если обе реализации GO не являются Buggy, правильные реализации, Toolchain1 и ToolChain2 должны вести себя точно так же. В частности, при представленных источниках GO 1.x вывод ToolChain1 (ToolChain2) и вывод ToolChain2 (ToolChain3) должны быть идентичными, что означает Toolchain2 и Toolchain3.

По крайней мере, это идея. Создание этого истинно на практике требовало удаления пары непреднамеренных входов:

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

Библиотеки начальной загрузки.Любая библиотека, используемая компилятором, которая может выбирать из нескольких различных правильных выходов, может изменить свой вывод с одной версии go на следующую. Если это изменение вывода библиотеки вызывает изменение вывода компилятора, то Toolchain1 и Toolchain2 не будут семантически идентичными, а Toolchain2 и Toolchain3 не будут идентичны битом.

Канонический пример - этоsortпакет, который может размещать элементы, которые сравнивают равные влюбой заказ, который ему нравитсяПолем Регистрационно -реестр может сортировать для расстановки приоритетов часто используемых переменных, а линкер сортирует символы в разделе данных по размеру. Чтобы полностью устранить любой эффект из алгоритма сортировки, используемая функция сравнения никогда не должна сообщать о двух различных элементах как равные. На практике этот инвариант оказался слишком обременительным, чтобы навязывать каждое использование в составе инструментов, поэтому вместо этого мы договорились скопировать GO 1.xsortупаковка в дерево источника, которое представлено компилятору начальной загрузки. Таким образом, компилятор использует тот же алгоритм сортировки при использовании инструментов Bootstrap, как и при создании с самим собой.

Еще один пакет, который мы должны были скопировать, былcompress/zlib, поскольку линкера записывает сжатую информацию отладки, а оптимизация в библиотеках сжатия может изменить точный выход. Со временем мыДобавлены и другие пакеты в этот список тожеПолем Этот подход имеет дополнительное преимущество, позволяя компилятору GO 1.x использовать новые API, добавленные в эти пакеты, за счет того, что эти пакеты должны быть записаны для компиляции с более старыми версиями GO.

Воспроизводимость в GO 1.20

Работайте над GO 1.20, подготовленным как для простых воспроизводимых сборок, так и дляУправление инструментамиудалив еще два релевантных входа из сборки инструментов.

Хост C Инструментальный инструмент.Некоторые идут пакеты, особенноnet, по умолчаниюс использованиемcgoВ большинстве операционных систем. В некоторых случаях, таких как macOS и Windows, вызывая системы DLL с использованием системыcgoэто единственный надежный способ разрешения имен хоста. Когда мы используемcgoОднако, мы вызовываем на инструментальном положении Host C (что означает конкретный компилятор C и библиотеку C), а различные инструменты имеют разные алгоритмы компиляции и код библиотеки, создавая различные выходы. График сборки дляcgoПакет выглядит как:

Таким образом, набор инструментов хоста C является соответствующим вводом для предварительно скомпилированнойnet.aЭто поставляется с набором инструментов. Для GO 1.20 мы решили исправить это, удаливnet.aот набора инструментов. То есть GO 1.20 прекратил доставку предварительно скомпилированных пакетов, чтобы заселить кэш сборки. Теперь в первый раз, когда программа использует пакетnet, The Toolchain GO компилирует его с использованием инструментов локальной системы C и кэша, которые возникают.

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

Одна из причин, по которой мы отправили предварительно скомпилированнуюnetВ первую очередь пакет должен был позволить строительным программам, которые использовали сеть пакетов даже в системах без установленного инструментального оборудования C. Если нет предварительно скомпилированного пакета, что происходит в этих системах? Ответ варьируется в зависимости от операционной системы, но во всех случаях мы договорились, чтобы The Toolchain GO продолжил хорошо работать для создания программ Pure GO без хоста C Toolchain.

  • На MacOS мы переписали сеть пакета, используя базовые механизмы, которые CGO использует, без какого -либо фактического C -кода. Это позволяет избежать выбора набора инструментов хоста C, но все же излучает двоичный файл, который относится к требуемой системной DLL. Этот подход возможен только потому, что каждый Mac имеет одинаковые динамические библиотеки. Создание чистого использования пакета MacOS MacOS CGO. Также означало, что скрепившиеся исполняемые файлы macOS теперь используют системные DLL для доступа к сети, разрешив давний запрос функций.

  • В Windows сеть пакетов уже прямо использует DLL без кода C, поэтому ничего не нужно изменить.

  • В системах UNIX мы не можем предположить, что конкретный интерфейс DLL для сетевого кода, но версия Pure GO отлично подходит для систем, которые используют типичные настройки IP и DNS. Кроме того, гораздо проще установить C -инструментальный набор C в системах UNIX, чем на MacOS и особенно Windows. Мы изменилиgoкомандовать, чтобы включить или отключитьcgoАвтоматически на основе того, установлен ли система на инструментальной программе C. Системы Unix без инструментального оборудования C возвращаются к версии Pure Go Package Net, и в тех редких случаях, когда этого недостаточно, они могут установить C -инструмент.

Установив предварительно скомпилированные пакеты, единственной частью инструментального оборудования GO, которая все еще зависела от хоста C Инструментальной нагрузки, была двоичная карта, созданная с использованием пакетной сети, в частности, вgoкомандование С улучшениями macOS теперь было жизнеспособным построить эти команды сcgoОтключил, полностью удалив набор инструментов хоста C в качестве входа, но мы оставили этот последний шаг для GO 1.21.

Динамический линкер хоста.Когда программы используютcgoВ системе, использующей динамически связанные библиотеки C, полученные двоичные файлы содержат путь к динамическому линкеру системы, что -то вроде/lib64/ld-linux-x86-64.so.2Полем Если путь неверен, двоичные файлы не бегают. Обычно каждая комбинация операционной системы/архитектуры имеет единый правильный ответ для этого пути. К сожалению, Linux на основе мусульман, такие как Alpine Linux, используют другой динамический линкер, чем Linux на основе GLIBC, такие как Ubuntu. Чтобы заставить запустить вообще на Alpine Linux, в процессе начальной загрузки выглядел так:

Программа Bootstrap CMD/DIST проверила динамический линкер локальной системы и написал это значение в новый исходный файл, собранный вместе с остальными источниками линкера, эффективно усердно кодируя это по умолчанию в сам линкер. Затем, когда линкер построил программу из набора скомпилированных пакетов, он использовал это по умолчанию. Результатом является то, что набором инструментов GO, построенный на альпийском языке, отличается от инструментального оборудования, созданного на Ubuntu: конфигурация хоста является соответствующим входом в сборку инструментов. Это проблема воспроизводимости, а также проблема портативности: инструмент GO, созданный на альпийском языке, не строит рабочие двоичные файлы и даже не работает на Ubuntu, и наоборот.

Для GO 1.20 мы сделали шаг к решению проблемы воспроизводимости, изменив линкер, чтобы проконсультироваться с конфигурацией хоста, когда он работает, вместо того, чтобы иметь жесткий кодировку по умолчанию в Toolchain Time:

Это установило переносимость бинарного линкера на Alpine Linux, хотя и не общий инструмент, так какgoКоманда все еще используется пакетnetи поэтомуcgoи, следовательно, имел динамическую ссылку на линкер в своем собственном бинарнике. Так же, как в предыдущем разделе, составлениеgoкомандование безcgoВключено это исправить, но мы оставили это изменение для GO 1.21. (Мы не чувствовали, что в цикле GO 1.20 осталось достаточно времени, чтобы проверить такое, чтобы правильно измениться.)

Воспроизводимость в GO 1.21

Для GO 1.21 цель идеальной воспроизводимости была в поле зрения, и мы позаботились о оставшихся, в основном небольших, соответствующих ресурсах, которые остались.

Host C Набор инструментов и динамический линкер.Как обсуждалось выше, GO 1.20 предпринял важные шаги к удалению инструментального набора хоста C и динамического линкера в качестве соответствующих входов. GO 1.21 Завершено удаление этих соответствующих входов, построив инструментcgoнеполноценный. Эта улучшенная переносимость инструментального оборудования также: GO 1.21 - это первое выпуск GO, где стандартный инструмент GO работает, не модифицированным в Alpine Linux Systems.

Удаление этих соответствующих входов позволило перекрестно-компиляции go chachain инструментария из другой системы без каких-либо потерь в функциональности. Это, в свою очередь, улучшило безопасность цепочки поставок в GO Toolchain: теперь мы можем создать инструменты GO для всех целевых систем, используя доверенную систему Linux/x86-64, вместо того, чтобы договориться об отдельной доверенной системе для каждой цели. В результате GO 1.21 - первый релиз, включающий опубликованные двоичные файлы для всех систем вgo.dev/dl/Полем

Справочник источника.Программы GO включают полные пути в метаданные среды выполнения и отладку, так что, когда программа вылетает или запускается в отладчике, следы стека включают полный путь к исходному файлу, а не только имя файла в неопределенном каталоге. К сожалению, в том числе полный путь делает каталог, в котором исходный код хранится соответствующий вход в сборку. Чтобы исправить это, go 1.21 изменил сборки инструментов релиза, чтобы установить команды, такие как компилятор, использующийgo install -trimpath, который заменяет исходный каталог на путь модуля кода. Если выпущенный компилятор вылетает, The Stack Trace будет печатать пути, какcmd/compile/main.goвместо/home/user/go/src/cmd/compile/main.goПолем

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

Операционная система хоста.Пути в системах Windows обработаны, какcmd\compile\main.goПолем Другие системы используют прямые черты, напримерcmd/compile/main.goПолем Хотя более ранние версии GO нормализовали большинство этих путей для использования прямых чертов, одно несогласованность повторно оказалась, что привело к тому, что в Windows несколько различных сборки инструмента. Мы нашли и исправили ошибку.

Хост архитектура.Go запускается по различным системам ARM и может излучать код, используя библиотеку программного обеспечения для математики с плавающей точкой (SWFP) или с помощью аппаратных инструкций с плавающей точкой (HWFP). Уходы инструментов дефолта в тот или иной режим обязательно будут отличаться. Как мы видели с динамическим линкером ранее, процесс начальной загрузки GO проверял систему сборки, чтобы убедиться, что результирующий инструмент работал над этой системой. По историческим причинам правило было «предположить SWFP, если только сборка не работает в системе ARM с оборудованием с плавающей точкой», с перекрестными считанными инструментами, предполагающими SWFP.

Подавляющее большинство систем ARM сегодня имеет оборудование с плавающей точкой, поэтому это внесло ненужную разницу между национально скомпилированными и перекрестными считанными наборами инструментов, и в качестве дальнейшего морщин, сборки Windows всегда предполагают HWFP, что делает решение операционной системы. Мы изменили правило, чтобы «предположить HWFP, если только сборка не работает в системе ARM без оборудования для плавающей точки». Таким образом, кросс-компиляция и основаны на современных системах ARM производят идентичные инструменты.

Логика упаковки.Весь код для создания фактических архивов инструментов, которые мы публикуем для загрузки, жили в отдельном репозитории GIT, golang.org/x/build, и точные детали того, как упаковываются архивы, со временем меняются. Если вы хотите воспроизвести эти архивы, вам нужно было иметь правильную версию этого репозитория. Мы удалили этот соответствующий ввод, перенесли код, чтобы упаковать архивы в основное дерево источника GO, какcmd/distpackПолем По состоянию на GO 1.21, если у вас есть источники для данной версии GO, у вас также есть источники для упаковки архивов. Репозиторий golang.org/x/build больше не является соответствующим входом.

Идентификаторы пользователя.Архив TAR, которые мы разместили для загрузки, были построены из распределения, записанного в файловую систему, и с использованиемtar.FileInfoHeaderкопирует идентификаторы пользователя и групп из файловой системы в файл TAR, заставляя пользователя, выполняющего строительство соответствующего ввода. Мы изменили архивирующий код, чтобы очистить их.

Текущее время.Как и в случае с идентификаторами пользователей, архивы TAR и ZIP, которые мы разместили для загрузки, были созданы путем копирования времени модификации файловой системы в архивы, что сделало текущее время соответствующим вводом. Мы могли бы очистить время, но мы подумали, что это будет выглядеть удивительно и, возможно, даже сломать некоторые инструменты, чтобы использовать время Unix или MS-DOS. Вместо этого мы изменили файл GO/версии, хранящийся в репозитории, чтобы добавить время, связанное с этой версией:

$ cat go1.21.0/VERSION
go1.21.0
time 2023-08-04T20:14:06Z
$

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

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

ОС-специфические пакеты.Мы используем инструменты XcodepkgbuildиproductbuildЧтобы создать загружаемый установщик MacOS PKG, и мы используем WIX для создания загружаемого установщика Windows MSI. Мы не хотим, чтобы проверки нуждались в тех же самых точных версиях этих инструментов, поэтому мы использовали тот же подход, что и для криптографических ключей подписания, написав проверку, который может заглянуть в пакеты, и проверяли, что файлы инструментов chanchain точно так же, как и ожидалось.

Проверка инструментов GO

Этого недостаточно, чтобы сделать инструменты, воспроизводимые один раз. Мы хотим убедиться, что они остаются воспроизводимыми, и мы хотим, чтобы другие могли легко их воспроизвести.

Чтобы сохранить себя честными, теперь мы строим все распределения GO для системы доверенного Linux/x86-64 и системы Windows/x86-64. За исключением архитектуры, две системы имеют почти ничего общего. Две системы должны производить идентичные архивы битов, иначе мы не перейдем к выпуску.

Чтобы позволить другим проверить, что мы честны, мы написали и опубликовали проверку,golang.org/x/build/cmd/gorebuildПолем Эта программа начнется с исходного кода в нашем хранилище GIT и восстановит текущие версии GO, проверив, что они соответствуют размещению архивов.go.dev/dlПолем Большинство архивов необходимы для сопоставления битов для бита. Как упомянуто выше, есть три исключения, где используется более расслабленная проверка:

  • Ожидается, что файл macos tar.gz будет отличаться, но затем проверка сравнивает содержимое внутри. Восстановленные и размещенные копии должны содержать одни и те же файлы, и все файлы должны соответствовать точно, за исключением исполняемых двоичных файлов. Исполняемые двоичные файлы должны соответствовать точно так же после счистки кодовых подписей.

  • Установщик MacOS PKG не перестраивается. Вместо этого проверчик считывает файлы внутри установщика PKG и проверяет, что он соответствует macos tar.gz точно, опять же, после того, как подписание подписи кода. В долгосрочной перспективе создание PKG достаточно тривиально, чтобы его потенциально могли быть добавлены в CMD/Distpack, но Verifier все равно придется проанализировать файл PKG для запуска исполнительного сравнения кода, подписываемого.

  • Установщик Windows MSI не перестраивается. Вместо этого проверка вызывает программу LinuxmsiextractЧтобы извлечь файлы внутри и проверить, что они точно соответствуют восстановлению файла Windows. В долгосрочной перспективе, возможно, создание MSI может быть добавлено в CMD/DISTPACK, а затем Verifier может использовать битовое сравнение MSI.

Мы бежимgorebuildночью, публикуя результаты вgo.dev/rebuild, и, конечно, кто -нибудь еще может запустить это.

Проверка инструментария Ubuntu's Go

Легко воспроизводимые сборки GO Toolchain должны означать, что двоичные файлы в инструментах, размещенных на Go.Dev, соответствуют двоичным файлам, включенным в другие системы упаковки, даже если эти пакеры строят из источника. Даже если пакеты скомпилировали с различными конфигурациями или другими изменениями, легко воспроизводимые сборки все равно должны легко воспроизводить их двоичные файлы. Чтобы продемонстрировать это, давайте воспроизводим Ubuntugolang-1.21версия пакета1.21.0-1Для Linux/x86-64.

Для начала нам нужно скачать и извлечь пакеты Ubuntu, которыеAR (1) АрхивСодержит ZSTD-сжатый TAR Archives:

$ mkdir deb
$ cd deb
$ curl -LO http://mirrors.kernel.org/ubuntu/pool/main/g/golang-1.21/golang-1.21-src_1.21.0-1_all.deb
$ ar xv golang-1.21-src_1.21.0-1_all.deb
x - debian-binary
x - control.tar.zst
x - data.tar.zst
$ unzstd < data.tar.zst | tar xv
...
x ./usr/share/go-1.21/src/archive/tar/common.go
x ./usr/share/go-1.21/src/archive/tar/example_test.go
x ./usr/share/go-1.21/src/archive/tar/format.go
x ./usr/share/go-1.21/src/archive/tar/fuzz_test.go
...
$

Это был исходный архив. Теперь бинарный архив AMD64:

$ rm -f debian-binary *.zst
$ curl -LO http://mirrors.kernel.org/ubuntu/pool/main/g/golang-1.21/golang-1.21-go_1.21.0-1_amd64.deb
$ ar xv golang-1.21-src_1.21.0-1_all.deb
x - debian-binary
x - control.tar.zst
x - data.tar.zst
$ unzstd < data.tar.zst | tar xv | grep -v '/$'
...
x ./usr/lib/go-1.21/bin/go
x ./usr/lib/go-1.21/bin/gofmt
x ./usr/lib/go-1.21/go.env
x ./usr/lib/go-1.21/pkg/tool/linux_amd64/addr2line
x ./usr/lib/go-1.21/pkg/tool/linux_amd64/asm
x ./usr/lib/go-1.21/pkg/tool/linux_amd64/buildid
...
$

Ubuntu разбивает нормальное дерево Go на две половинки, в /ср /шаре/go-1.21 и /usr/lib/go-1.21. Давайте собрать их вместе:

$ mkdir go-ubuntu
$ cp -R usr/share/go-1.21/* usr/lib/go-1.21/* go-ubuntu
cp: cannot overwrite directory go-ubuntu/api with non-directory usr/lib/go-1.21/api
cp: cannot overwrite directory go-ubuntu/misc with non-directory usr/lib/go-1.21/misc
cp: cannot overwrite directory go-ubuntu/pkg/include with non-directory usr/lib/go-1.21/pkg/include
cp: cannot overwrite directory go-ubuntu/src with non-directory usr/lib/go-1.21/src
cp: cannot overwrite directory go-ubuntu/test with non-directory usr/lib/go-1.21/test
$

Ошибки жалуются на копирование символов, которые мы можем игнорировать.

Теперь нам нужно скачать и извлечь источники вверх по течению:

$ curl -LO https://go.googlesource.com/go/+archive/refs/tags/go1.21.0.tar.gz
$ mkdir go-clean
$ cd go-clean
$ curl -L https://go.googlesource.com/go/+archive/refs/tags/go1.21.0.tar.gz | tar xzv
...
x src/archive/tar/common.go
x src/archive/tar/example_test.go
x src/archive/tar/format.go
x src/archive/tar/fuzz_test.go
...
$

Чтобы пропустить пробные и ошибки, оказывается, что Ubuntu строитGO386=softfloat, который заставляет использовать программную плавучесть при компиляции для 32-битного x86, и полоски (удаляет таблицы символов), полученные в результате бинарны эльфов. Давайте начнем сGO386=softfloatстроить:

$ cd src
$ GOOS=linux GO386=softfloat ./make.bash -distpack
Building Go cmd/dist using /Users/rsc/sdk/go1.17.13. (go1.17.13 darwin/amd64)
Building Go toolchain1 using /Users/rsc/sdk/go1.17.13.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building commands for host, darwin/amd64.
Building packages and commands for target, linux/amd64.
Packaging archives for linux/amd64.
distpack: 818d46ede85682dd go1.21.0.src.tar.gz
distpack: 4fcd8651d084a03d go1.21.0.linux-amd64.tar.gz
distpack: eab8ed80024f444f v0.0.1-go1.21.0.linux-amd64.zip
distpack: 58528cce1848ddf4 v0.0.1-go1.21.0.linux-amd64.mod
distpack: d8da1f27296edea4 v0.0.1-go1.21.0.linux-amd64.info
---
Installed Go for linux/amd64 in /Users/rsc/deb/go-clean
Installed commands in /Users/rsc/deb/go-clean/bin
*** You need to add /Users/rsc/deb/go-clean/bin to your PATH.
$

Это оставило стандартный пакет вpkg/distpack/go1.21.0.linux-amd64.tar.gzПолем Давайте распаковываем его и разделим двоичные файлы, чтобы соответствовать Ubuntu:

$ cd ../..
$ tar xzvf go-clean/pkg/distpack/go1.21.0.linux-amd64.tar.gz
x go/CONTRIBUTING.md
x go/LICENSE
x go/PATENTS
x go/README.md
x go/SECURITY.md
x go/VERSION
...
$ elfstrip go/bin/* go/pkg/tool/linux_amd64/*
$

Теперь мы можем различить инструмент Go, который мы создали на нашем Mac с помощью инструментального оборудования GO, который отправляет Ubuntu:

$ diff -r go go-ubuntu
Only in go: CONTRIBUTING.md
Only in go: LICENSE
Only in go: PATENTS
Only in go: README.md
Only in go: SECURITY.md
Only in go: codereview.cfg
Only in go: doc
Only in go: lib
Binary files go/misc/chrome/gophertool/gopher.png and go-ubuntu/misc/chrome/gophertool/gopher.png differ
Only in go-ubuntu/pkg/tool/linux_amd64: dist
Only in go-ubuntu/pkg/tool/linux_amd64: distpack
Only in go/src: all.rc
Only in go/src: clean.rc
Only in go/src: make.rc
Only in go/src: run.rc
diff -r go/src/syscall/mksyscall.pl go-ubuntu/src/syscall/mksyscall.pl
1c1
< #!/usr/bin/env perl
---
> #! /usr/bin/perl
...
$

Мы успешно воспроизводили исполнительные файлы пакета Ubuntu и определили полный набор оставшихся изменений:

  • Различные метаданные и поддерживающие файлы были удалены.

  • Аgopher.pngФайл был изменен. При ближайшем рассмотрении они идентичны, за исключением встроенной временной метки, которую Ubuntu обновила. Возможно, сценарии упаковки Ubuntu рекомпрессировали PNG инструментом, который переписывает метку времени, даже если она не может улучшить существующее сжатие.

  • Двоичные файлыdistиdistpack, которые построены во время начальной загрузки, но не включены в стандартные архивы, были включены в пакет Ubuntu.

  • План 9 строит сценарии (*.rc) были удалены, хотя сценарии Windows строят (сценарии (*.bat) оставаться.

  • mksyscall.plи семь других сценариев Perl, не показанных, изменили свои заголовки.

В частности, обратите внимание, что мы реконструировали бинарные бинарны инструментов: они вообще не отображаются в Diff. То есть мы доказали, что двоичные файлы Ubuntu Go точно соответствуют источникам вверх по течению.

Еще лучше, мы доказали это без использования какого -либо программного обеспечения Ubuntu вообще: эти команды были запущены на Mac, иunzstdиelfstripпрограммы коротких ходов. Сложный злоумышленник может вставить вредоносной код в пакет Ubuntu, изменив инструменты создания пакета. Если они это сделают, воспроизведение пакета Go Ubuntu из чистых источников, используя эти вредоносные инструменты, все равно будет производить битовые идентичные копии вредоносных пакетов.

Эта атака была бы невидимой для такой перестройки, очень похоже наАтака компилятора Кена ТомпсонаПолем Проверка пакетов Ubuntu с использованием программного обеспечения No Ubuntu вообще является гораздо более сильной проверкой. Идеально воспроизводимые сборки GO, которые не зависят от неожиданных деталей, таких как операционная система хоста, архитектура хоста и набор инструментов Host C, - это то, что делает возможным эта более сильная проверка.

(Помимо исторической записи, Кен Томпсон однажды сказал мне, что его атака была фактически обнаружена, потому что сборщик компилятора перестал воспроизводим. У него была ошибка: константа струна в заднем ход, добавленную к компилятору, была несовершенно обработанной и выросла одним байтом NUL каждый раз, компилированное. Компилирование. Компилировал. Компилировал, что он не смотрел на сборку. Бэкдор компилятора вообще не воспроизводил себя в выходе сборки, поэтому сборка этого вывода удалял задний ход.)

Заключение

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

Идеальная воспроизводимость (когда исходные файлы являются единственным соответствующим входом сборки) возможна только для программ, которые создают себя, например, компиляторные инструменты. Это высокая, но достойная цель именно потому, что в остальном самостоятельный компилятор в противном случае довольно сложно проверить. Идеальная воспроизводимость GO означает, что, если при условии, что пакеты не изменяют исходный код, каждое переупакование GO 1.21.0 для Linux/x86-64 (заменить вашу любимую систему) в любой форме должна распространять точно одинаковые двоичные файлы, даже если они все строят из источника. Мы видели, что это не совсем верно для Ubuntu Linux, но идеальная воспроизводимость по-прежнему позволяет нам воспроизводить упаковку Ubuntu, используя совершенно другую систему, не являющуюся Ubuntu.

В идеале все программное обеспечение с открытым исходным кодом, распределенное в двоичной форме, будет иметь простые для реализации сборки. На практике, как мы видели в этом посте, непреднамеренные входные данные очень легко протекать в сборки. Для программ GO, которые не нужныcgo, воспроизводимая сборка так же просто, как и компиляция сCGO_ENABLED=0 go build -trimpathПолем ОтключениеcgoУдаляет наборы инструментов хоста C в качестве соответствующего ввода, и-trimpathУдаляет текущий каталог. Если ваша программа действительно нужнаcgo, вам нужно организовать определенную версию инструментального оборудования для хоста C перед запускомgo build, например, путем запуска сборки в определенной виртуальной машине или изображении контейнера.

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


Расс Кокс

ФотоАлекс ПудокнаНеспособный

Эта статья доступна наБлог GOПод CC по лицензии 4,0.


Оригинал
PREVIOUS ARTICLE