Обнаружение бэкдора в XZ Utils весной 2024 года поразило опенсорс-сообщество и подняло серьёзные вопросы о безопасности цепочки поставок ПО. В этом посте мы изучим, могло ли улучшение практик работы с пакетами Debian помочь в выявлении этой угрозы, приведём руководство по аудиту пакетов и предложим улучшения на будущее.

Бэкдор XZ в версиях 5.6.0/5.6.1 быстро попал во многие крупные дистрибутивы Linux наподобие Debian и Fedora, но, к счастью, не добрался до многих реальных пользователей, потому что благодаря героическому усердию Андреса Фройнда релизы с бэкдором были быстро удалены. Нам крайне повезло, что мы выявили регрессию производительности SSH в полсекунды, уделили время её трассировке, обнаружили зловредный код в загружаемой SSH библиотеке XZ и быстро сообщили о нём различным командам безопасников для принятия быстрых координированных мер защиты.

Этот эпизод заставил разработчиков ПО задаться следующими вопросами:

  • Почему ни один из упаковщиков дистрибутивов Linux не заметил ничего странного при импорте новой версии XZ 5.6.0/5.6.1 из апстрима?

  • Легко ли проводить аудит современной цепочки поставок в самых популярных дистрибутивах Linux?

  • Возможно ли наличие других подобных бэкдоров, которые мы пока не выявили?

Я разработчик Debian Developer, поэтому решил провести аудит пакета xz в Debian, поделиться в этом посте своей методологией и находками, а также предложить способы повышения безопасности цепочки поставок конкретно в Debian.

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

Скачивание Debian и пакеты исходников апстрима

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

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

Первым делом нужно скачать последнюю версию и несколько прошлых версий пакета из архива Debian. Проще всего это сделать при помощи debsnap. Показанная ниже команда скачает все пакеты исходников xz-utils для Debian, начиная с релиза Debian 5.2.4-1:

$ debsnap --verbose --first 5.2.4-1 xz-utils
Getting json https://snapshot.debian.org/mr/package/xz-utils/
...
Getting dsc file xz-utils_5.2.4-1.dsc: https://snapshot.debian.org/file/a98271e4291bed8df795ce04d9dc8e4ce959462d
Getting file xz-utils_5.2.4.orig.tar.xz.asc: https://snapshot.debian.org/file/59ccbfb2405abe510999afef4b374cad30c09275
Getting file xz-utils_5.2.4-1.debian.tar.xz: https://snapshot.debian.org/file/667c14fd9409ca54c397b07d2d70140d6297393f
source-xz-utils/xz-utils_5.2.4-1.dsc:
      Good signature found
   validating xz-utils_5.2.4.orig.tar.xz
   validating xz-utils_5.2.4.orig.tar.xz.asc
   validating xz-utils_5.2.4-1.debian.tar.xz
All files validated successfully.

После завершения работы debsnap появится подпапка source-<имя пакета> со следующими типами файлов:

  • *.orig.tar.xz: исходный код из апстрима

  • *.orig.tar.xz.asc: отдельная подпись (если апстрим подписывает свои релизы)

  • *.debian.tar.xz: исходники пакета Debian, то есть содержимое подпапки debian/

  • *.dsc: файл системы управления версиями Debian, в том числе подпись разработчика/мейнтейнера Debian

Пример:

$ ls -1 source-xz-utils/
...
xz-utils_5.6.4.orig.tar.xz
xz-utils_5.6.4.orig.tar.xz.asc
xz-utils_5.6.4-1.debian.tar.xz
xz-utils_5.6.4-1.dsc
xz-utils_5.8.0.orig.tar.xz
xz-utils_5.8.0.orig.tar.xz.asc
xz-utils_5.8.0-1.debian.tar.xz
xz-utils_5.8.0-1.dsc
xz-utils_5.8.1.orig.tar.xz
xz-utils_5.8.1.orig.tar.xz.asc
xz-utils_5.8.1-1.1.debian.tar.xz
xz-utils_5.8.1-1.1.dsc
xz-utils_5.8.1-1.debian.tar.xz
xz-utils_5.8.1-1.dsc
xz-utils_5.8.1-2.debian.tar.xz
xz-utils_5.8.1-2.dsc

Верификация подлинности апстрима и исходников Debian при помощи подписей OpenPGP

Как мы видели в выводе debsnap, он уже автоматически верифицирует, что скачанные файлы соответствуют подписям OpenPGP. Чтобы чётко понимать, какие файлы были аутентифицированы и с какими ключами, мы должны верифицировать подпись упаковщиков Debian при помощи следующей команды:

$ gpg --verify --auto-key-retrieve --keyserver hkps://keyring.debian.org xz-utils_5.8.1-2.dsc
gpg: Signature made Fri Oct  3 22:04:44 2025 UTC
gpg:                using RSA key 57892E705233051337F6FDD105641F175712FA5B
gpg: requesting key 05641F175712FA5B from hkps://keyring.debian.org
gpg: key 7B96E8162A8CF5D1: public key "Sebastian Andrzej Siewior" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: Good signature from "Sebastian Andrzej Siewior" [unknown]
gpg:                 aka "Sebastian Andrzej Siewior <bigeasy@linutronix.de>" [unknown]
gpg:                 aka "Sebastian Andrzej Siewior <sebastian@breakpoint.cc>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 6425 4695 FFF0 AA44 66CC  19E6 7B96 E816 2A8C F5D1
     Subkey fingerprint: 5789 2E70 5233 0513 37F6  FDD1 0564 1F17 5712 FA5B

Подпись tarball апстрима (в случае её наличия) можно верифицировать так:

$ gpg --verify --auto-key-retrieve xz-utils_5.8.1.orig.tar.xz.asc
gpg: assuming signed data in 'xz-utils_5.8.1.orig.tar.xz'
gpg: Signature made Thu Apr  3 11:38:23 2025 UTC
gpg:                using RSA key 3690C240CE51B4670D30AD1C38EE757D69184620
gpg: key 38EE757D69184620: public key "Lasse Collin <lasse.collin@tukaani.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: Good signature from "Lasse Collin <lasse.collin@tukaani.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 3690 C240 CE51 B467 0D30  AD1C 38EE 757D 6918 4620

Необходимо помнить, что это лишь доказывает наличие ключа, создавшего валидную подпись для этого содержимого. Подлинность самих ключей необходимо валидировать отдельно, и до этого не стоит доверять тому, что ключи действительно принадлежат этим людям. Это можно сделать, например, обратившись к веб-сайту апстрима за опубликованными фингерпринтами ключей, или к Debian keyring для разработчиков и мейнтейнеров Debian. Также можно положиться на «сеть доверия» OpenPGP.

Верификация подлинности исходников апстрима при помощи сравнения контрольных сумм

Если соответствующий апстрим не публикует подписи релизов, то наилучшим способом проверки подлинности исходников в Debian будет скачивание исходников непосредственно из апстрима и сравнение контрольных сумм sha256.

Это можно сделать при помощи файла debian/watch, находящегося внутри пакета Debian; он определяет, откуда были скачаны исходники апстрима. Взяв ситуацию из примера выше, мы можем распаковать последние исходники Debian, а затем запустить uscan для скачивания:

$ tar xvf xz-utils_5.8.1-2.debian.tar.xz
...
debian/rules
debian/source/format
debian/source.lintian-overrides
debian/symbols
debian/tests/control
debian/tests/testsuite
debian/upstream/signing-key.asc
debian/watch
...

$ uscan --download-current-version --destdir /tmp
Newest version of xz-utils on remote site is 5.8.1, specified download version is 5.8.1
gpgv: Signature made Thu Apr  3 11:38:23 2025 UTC
gpgv:                using RSA key 3690C240CE51B4670D30AD1C38EE757D69184620
gpgv: Good signature from "Lasse Collin <lasse.collin@tukaani.org>"
Successfully symlinked /tmp/xz-5.8.1.tar.xz to /tmp/xz-utils_5.8.1.orig.tar.xz.

Оригинальные файлы, скачанные из апстрима, теперь находятся в /tmp вместе с файлами, переименованными для соответствия стандартам Debian. Для всего скачанного мы можем сравнить контрольные суммы sha256 файлов, а также то, что указано в файле .dsc:

$ ls -1 /tmp/
xz-5.8.1.tar.xz
xz-5.8.1.tar.xz.sig
xz-utils_5.8.1.orig.tar.xz
xz-utils_5.8.1.orig.tar.xz.asc

$ sha256sum xz-utils_5.8.1.orig.tar.xz /tmp/xz-5.8.1.tar.xz
0b54f79df85912504de0b14aec7971e3f964491af1812d83447005807513cd9e  xz-utils_5.8.1.orig.tar.xz
0b54f79df85912504de0b14aec7971e3f964491af1812d83447005807513cd9e  /tmp/xz-5.8.1.tar.xz

$ grep -A 3 Sha256 xz-utils_5.8.1-2.dsc
Checksums-Sha256:
 0b54f79df85912504de0b14aec7971e3f964491af1812d83447005807513cd9e 1461872 xz-utils_5.8.1.orig.tar.xz
 4138f4ceca1aa7fd2085fb15a23f6d495d27bca6d3c49c429a8520ea622c27ae 833 xz-utils_5.8.1.orig.tar.xz.asc
 3ed458da17e4023ec45b2c398480ed4fe6a7bfc1d108675ec837b5ca9a4b5ccb 24648 xz-utils_5.8.1-2.debian.tar.xz

В показанном выше примере контрольная сумма 0b54f79df85... одинакова для всех файлов, так что они совпадают.

Переупакованные исходники апстрима верифицировать не так легко

Стоит отметить, что в редких случаях uscan может переупаковывать некоторые исходники апстрима, например, чтобы исключить файлы, не отвечающие требованиям авторского права и лицензирования Debian. Список этих файлов и путей будет указан в разделе Files-Excluded файла debian/copyright. Бывают и другие ситуации, когда присутствующий в исходникам апстрима файл в Debian побитово неравен тому, который опубликован апстримом. Если контрольные суммы не совпадают, опытный разработчик Debian должен проверить все параметры пакетов (например, debian/source/options), чтобы убедиться, что для такого расхождения есть веская и предумышленная причина.

Проверка изменений между двумя пакетами исходников при помощи diffoscope

Diffoscope — невероятно мощный и удобный инструмент для сравнения произвольных файлов. Например, для просмотра в формате HTML отчёта о различиях между двумя релизами XZ нужно выполнить следующую команду:

diffoscope --html-dir xz-utils-5.6.4_vs_5.8.0 xz-utils_5.6.4.orig.tar.xz xz-utils_5.8.0.orig.tar.xz
browse xz-utils-5.6.4_vs_5.8.0/index.html
Inspecting diffoscope output of differences between two XZ Utils releases

Если изменений много и для поиска потенциальных проблем с безопасностью вы хотите использовать LLM, то сгенерируйте отчёт для разницы апстрима и пакета Debian в Markdown следующими командами:

diffoscope --markdown diffoscope-debian.md xz-utils_5.6.4-1.debian.tar.xz xz-utils_5.8.1-2.debian.tar.xz
diffoscope --markdown diffoscope.md xz-utils_5.6.4.orig.tar.xz xz-utils_5.8.0.orig.tar.xz

Созданные этими командами файлы Markdown можно затем передать LLM вместе с примерно таким промптом:

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

Просмотр пакетов исходников Debian в системе контроля версий

На данный момент всего 93% пакетов исходников Debian отслеживаются в git в инстансе GitLab Debian по адресу salsa.debian.org. Некоторые важные пакеты, например Coreutils и Bash, вообще не используют контроль версий, потому что их мейнтейнеры, очевидно, не видят ценности в применении git для упаковки Debian, а политика Debian этого не требует. Таким образом, единственный надёжный и согласованный способ аудита изменений в пакетах Debian заключается в сравнении полных версий из архива, как это показано выше.

Однако в случае пакетов, которые хостятся на Salsa, можно проверить историю git, чтобы глубже понимать, что, когда и зачем изменилось. Местоположение пакетов, использующих контроль версий, можно найти в заголовке Git-Vcs файла debian/control. В случае xz-utils это salsa.debian.org/debian/xz-utils.

Стоит отметить, что в политике Debian ничего не говорится о том, как следует использовать Salsa и какую структуру репозитория git и практики разработки применять. На практике, большинство пакетов следует предложению DEP-14, используя git-buildpackage в качестве инструмента управления изменениями, пушинга и пулинга их между апстримом и salsa.debian.org.

Чтобы получить исходники XZ Utils, нужно выполнить следующее:

$ gbp clone https://salsa.debian.org/debian/xz-utils.git
gbp:info: Cloning from 'https://salsa.debian.org/debian/xz-utils.git'

На момент написания этого поста в истории git находилось следующее:

$ git log --graph --oneline
* bb787585 (HEAD -> debian/unstable, origin/debian/unstable, origin/HEAD) Prepare 5.8.1-2
* 4b769547 d: Remove the symlinks from -dev package.
* a39f3428 Correct the nocheck build profile
* 1b806b8d Import Debian changes 5.8.1-1.1
* b1cad34b Prepare 5.8.1-1
* a8646015 Import 5.8.1
*   2808ec2d Update upstream source from tag 'upstream/5.8.1'
|\
| * fa1e8796 (origin/upstream/v5.8, upstream/v5.8) New upstream version 5.8.1
| * a522a226 Bump version and soname for 5.8.1
| * 1c462c2a Add NEWS for 5.8.1
| * 513cabcf Tests: Call lzma_code() in smaller chunks in fuzz_common.h
| * 48440e24 Tests: Add a fuzzing target for the multithreaded .xz decoder
| * 0c80045a liblzma: mt dec: Fix lack of parallelization in single-shot decoding
| * 81880488 liblzma: mt dec: Don't modify thr->in_size in the worker thread
| * d5a2ffe4 liblzma: mt dec: Don't free the input buffer too early (CVE-2025-31115)
| * c0c83596 liblzma: mt dec: Simplify by removing the THR_STOP state
| * 831b55b9 liblzma: mt dec: Fix a comment
| * b9d168ee liblzma: Add assertions to lzma_bufcpy()
| * c8e0a489 DOS: Update Makefile to fix the build
| * 307c02ed sysdefs.h: Avoid <stdalign.h> even with C11 compilers
| * 7ce38b31 Update THANKS
| * 688e51bd Translations: Update the Croatian translation
* | a6b54dde Prepare 5.8.0-1.
* | 77d9470f Add 5.8 symbols.
* | 9268eb66 Import 5.8.0
* |   6f85ef4f Update upstream source from tag 'upstream/5.8.0'
|\ \
| * | afba662b New upstream version 5.8.0
| |/
| * 173fb5c6 doc/SHA256SUMS: Add 5.8.0
| * db9258e8 Bump version and soname for 5.8.0
| * bfb752a3 Add NEWS for 5.8.0
| * 6ccbb904 Translations: Run "make -C po update-po"
| * 891a5f05 Translations: Run po4a/update-po
| * 4f52e738 Translations: Partially fix overtranslation in Serbian man pages
| * ff5d9447 liblzma: Count the extra bytes in LZMA/LZMA2 decoder memory usage
| * 943b012d liblzma: Use SSE2 intrinsics instead of memcpy() in dict_repeat()

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

Чтобы просматривать изменения только в ветви Debian, нужно выполнить git log --graph --oneline --first-parent or git log --graph --oneline -- debian.

Изменения в ветви Debian должны находиться только внутри подпапки debian/, что легко проверить такой командой:

$ git diff --stat upstream/v5.8
 debian/README.source             |  16 +++
 debian/autogen.sh                |  32 +++++
 debian/changelog                 | 949 ++++++++++++++++++++++++++
 ...
 debian/upstream/signing-key.asc  |  52 +++++++++
 debian/watch                     |   4 +
 debian/xz-utils.README.Debian    |  47 ++++++++
 debian/xz-utils.docs             |   6 +
 debian/xz-utils.install          |  28 +++++
 debian/xz-utils.postinst         |  19 +++
 debian/xz-utils.prerm            |  10 ++
 debian/xzdec.docs                |   6 +
 debian/xzdec.install             |   4 +
 33 files changed, 2014 insertions(+)

Источником всех файлов снаружи папки debian/ будет апстрим, и, например, команда git blame для них должна показать только коммиты апстрима:

$ git blame CMakeLists.txt
22af94128 (Lasse Collin 2024-02-12 17:09:10 +0200  1) # SPDX-License-Identifier: 0BSD
22af94128 (Lasse Collin 2024-02-12 17:09:10 +0200  2)
7e3493d40 (Lasse Collin 2020-02-24 23:38:16 +0200  3) ###############
7e3493d40 (Lasse Collin 2020-02-24 23:38:16 +0200  4) #
426bdc709 (Lasse Collin 2024-02-17 21:45:07 +0200  5) # CMake support for building XZ Utils

Если апстрим подписывает коммиты или теги, то их можно верифицировать, например, так:

$ git verify-tag v5.6.2
gpg: Signature made Wed 29 May 2024 09:39:42 AM PDT
gpg:                using RSA key 3690C240CE51B4670D30AD1C38EE757D69184620
gpg:                issuer "lasse.collin@tukaani.org"
gpg: Good signature from "Lasse Collin <lasse.collin@tukaani.org>" [expired]
gpg: Note: This key has expired!

Главное преимущество проверки изменений в git — это возможность просмотра подробной информации о каждом отдельном изменении; это гораздо удобнее, чем смотреть на огромный список без каких-либо объяснений. В этом примере для просмотра всех коммитов апстрима с предыдущего импорта в Debian следует просматривать диапазон коммитов от afba662b New upstream version 5.8.0 до fa1e8796 New upstream version 5.8.1 при помощи git log --reverse -p afba662b...fa1e8796. Однако гораздо лучше просматривать эти изменения будет исследованием диапазона при помощи визуальной программы для просмотра истории git наподобие gitk. Как бы то ни было, просмотр каждого отдельного изменения кода и чтение сообщения коммита git сильно упрощает проверку.

Browsing git history in gitk --all

Сравнение пакетов исходников Debian с содержимым git

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

Чтобы выявить такие различия, мы можем выполнять diff для пакетов исходников Debian, ранее скачанных при помощи debsnap (путь source-xz-utils/xz-utils_5.8.1-2.debian), и репозитория git, клонированного в предыдущем разделе (путь xz-utils):

DIFF

$ diff -u source-xz-utils/xz-utils_5.8.1-2.debian/ xz-utils/debian/
diff -u source-xz-utils/xz-utils_5.8.1-2.debian/changelog xz-utils/debian/changelog
--- debsnap/source-xz-utils/xz-utils_5.8.1-2.debian/changelog	2025-10-03 09:32:16.000000000 -0700
+++ xz-utils/debian/changelog	2025-10-12 12:18:04.623054758 -0700
@@ -5,7 +5,7 @@
   * Remove the symlinks from -dev, pointing to the lib package.
     (Closes: #1109354)

- -- Sebastian Andrzej Siewior <sebastian@breakpoint.cc>  Fri, 03 Oct 2025 18:32:16 +0200
+ -- Sebastian Andrzej Siewior <sebastian@breakpoint.cc>  Fri, 03 Oct 2025 18:36:59 +0200

В приведённом выше diff выяснилось, что метка времени в changelog, загруженной в Debian версии, отличается от той, которая была закоммичена в git. Это не злоумышленное действие, а недосмотр со стороны мейнтейнера, который, вероятно, сразу после загрузки выполнил не gbp tag, а какую-нибудь команду dch; в результате получились разные метки времени в git и том, что загрузили в Debian.

Создание синтетических репозиториев git пакетов Debian

Если в репозитории git нет пакетов Debian или если он отстаёт от того, что загружается в архив Debian, можно использовать фичу git-buildpackage import-dscs для создания синтетических коммитов git на основании файлов, скачанных debsnap; это гарантирует, что содержимое git будет полностью соответствовать тому, что было загружено в архив. Для импорта одной версии можно использовать gbp import-dsc (без «s» в конце). Пример такого вызова:

$ gbp import-dsc --verbose ../source-xz-utils/xz-utils_5.8.1-2.dsc
Version '5.8.1-2' imported under '/home/otto/debian/xz-utils-2025-09-29'

Пример истории коммитов из репозитория с коммитами, добавленными командой gbp import-dsc:

$ git log --graph --oneline
* 86aed07b (HEAD -> debian/unstable, tag: debian/5.8.1-2, origin/debian/unstable) Import Debian changes 5.8.1-2
* f111d93b (tag: debian/5.8.1-1.1) Import Debian changes 5.8.1-1.1
*   1106e19b (tag: debian/5.8.1-1) Import Debian changes 5.8.1-1
|\
| *   08edbe38 (tag: upstream/5.8.1, origin/upstream/v5.8, upstream/v5.8) Import Upstream version 5.8.1
| |\
| | * a522a226 (tag: v5.8.1) Bump version and soname for 5.8.1
| | * 1c462c2a Add NEWS for 5.8.1
| | * 513cabcf Tests: Call lzma_code() in smaller chunks in fuzz_common.h

Онлайн-пример репозитория с несколькими отсутствующими загрузками, добавленный при помощи gbp import-dsc, можно посмотреть в salsa.debian.org/otto/xz-utils-2025-09-29/-/network/debian%2Funstable

Пример репозитория, который был полностью сгенерирован при помощиgbp import-dscs, можно посмотреть в salsa.debian.org/otto/xz-utils-gbp-import-dscs-debsnap-generated/-/network/debian%2Flatest.

Также существует dgit, аналогичным образом создающий синтетическую историю git, позволяющую просматривать содержимое архива Debian при помощи инструментов git. Однако он делает упор на создание новых версий пакетов, поэтому скачивание при помощи dgit пакета, для которого ранее в dgit история не записывалась, отобразит только последние версии:

$ dgit clone xz-utils
canonical suite name for unstable is sid
starting new git history
last upload to archive: NO git hash
downloading http://ftp.debian.org/debian//pool/main/x/xz-utils/xz-utils\_5.8.1.orig.tar.xz...
downloading http://ftp.debian.org/debian//pool/main/x/xz-utils/xz-utils\_5.8.1.orig.tar.xz.asc...
downloading http://ftp.debian.org/debian//pool/main/x/xz-utils/xz-utils\_5.8.1-2.debian.tar.xz...
dpkg-source: info: extracting xz-utils in unpacked
dpkg-source: info: unpacking xz-utils_5.8.1.orig.tar.xz
dpkg-source: info: unpacking xz-utils_5.8.1-2.debian.tar.xz
synthesised git commit from .dsc 5.8.1-2
HEAD is now at f9bcaf7 xz-utils (5.8.1-2) unstable; urgency=medium
dgit ok: ready for work in xz-utils

$ dgit/sid ± git log --graph --oneline
*   f9bcaf7 xz-utils (5.8.1-2) unstable; urgency=medium 9 days ago (HEAD -> dgit/sid, dgit/dgit/sid)
|\
| * 11d3a62 Import xz-utils_5.8.1-2.debian.tar.xz 9 days ago
* 15dcd95 Import xz-utils_5.8.1.orig.tar.xz 6 months ago

В отличие от репозиториев, управляемых git-buildpackage, управляемые dgit репозитории не могут включать в себя историю git апстрима, поэтому менее полезны для аудита полной цепочки поставок ПО в git.

Сравнение пакетов исходников апстрима с содержимым git

Также стоит помнить о том, что пакеты исходников релизов апстрима, часто называемые tarball релизов (release tarball), не гарантированно имеют то же самое содержимое, что и репозиторий git апстрима. Проекты могут вырезать из своих tarball релизов данные тестов или дополнительные файлы разработки, чтобы не отправлять пользователям ненужные файлы; также проекты могут добавлять в tarball файлы документации или информацию о версионности, не хранящуюся в git. Есть небольшое меньшинство апстримов, вообще не использующее git, поэтому файлы без форматирования в tarball релизов по-прежнему остаются наименьшим общим знаменателем для всех проектов опенсорсного ПО, и при экспорте и импорте исходного кода необходимо с ним взаимодействовать.

В случае XZ tarball релиза был дополнительной информацией о версии, а также приличным объёмом предварительно сгенерированных файлов конфигурации компилятора. Выявление и сравнение различий между содержимым git и tarball, разумеется, можно выполнять вручную, запуская diff для неупакованного tarball и репозитория git с checkout. При использовании git-buildpackage разницу между содержимым git и содержимым tarball можно сделать наглядной непосредственно в коммите импорта.

Рассмотрим историю git из примера XZ:

* b1cad34b Prepare 5.8.1-1
* a8646015 Import 5.8.1
*   2808ec2d Update upstream source from tag 'upstream/5.8.1'
|\
| * fa1e8796 (debian/upstream/v5.8, upstream/v5.8) New upstream version 5.8.1
| * a522a226 (tag: v5.8.1) Bump version and soname for 5.8.1
| * 1c462c2a Add NEWS for 5.8.1

Коммит a522a226 представлял собой коммит релиза апстрима, который апстрим пометил как v5.8.1. Коммит слияния 2808ec2d применил содержимое новой ветви импорта апстрима для ветви Debian. Между ними находится особый коммит fa1e8796 New upstream version 5.8.1, помеченный как upstream/v5.8Этот коммит и тег есть только в репозитории пакетов Debian; они демонстрируют, какое содержимое импортируется в Debian. Всё это сгенерировано автоматически git-buildpackage при выполнении git import-orig --uscan для пакетов Debian с нужными настройками в debian/gbp.conf. Открыв этот коммит, можно чётко увидеть, как tarball релиза апстрима отличается от содержимого git апстрима (если отличается).

В случае XZ разница существенна. Ниже она показана полностью, потому что крайне любопытна:

$ git show --stat fa1e8796
commit fa1e8796dabd91a0f667b9e90f9841825225413a
       (debian/upstream/v5.8, upstream/v5.8)
Author: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
Date:   Thu Apr 3 22:58:39 2025 +0200

    New upstream version 5.8.1

 .codespellrc                     |    30 -
 .gitattributes                   |     8 -
 .github/workflows/ci.yml         |   163 -
 .github/workflows/freebsd.yml    |    32 -
 .github/workflows/netbsd.yml     |    32 -
 .github/workflows/openbsd.yml    |    35 -
 .github/workflows/solaris.yml    |    32 -
 .github/workflows/windows-ci.yml |   124 -
 .gitignore                       |   113 -
 ABOUT-NLS                        |     1 +
 ChangeLog                        | 17392 +++++++++++++++++++++
 Makefile.in                      |  1097 +++++++
 aclocal.m4                       |  1353 ++++++++
 build-aux/ci_build.bash          |   286 --
 build-aux/compile                |   351 ++
 build-aux/config.guess           |  1815 ++++++++++
 build-aux/config.rpath           |   751 +++++
 build-aux/config.sub             |  2354 +++++++++++++
 build-aux/depcomp                |   792 +++++
 build-aux/install-sh             |   541 +++
 build-aux/ltmain.sh              | 11524 ++++++++++++++++++++++
 build-aux/missing                |   236 ++
 build-aux/test-driver            |   160 +
 config.h.in                      |   634 ++++
 configure                        | 26434 ++++++++++++++++++++++
 debug/Makefile.in                |   756 +++++
 doc/SHA256SUMS                   |   236 --
 doc/man/txt/lzmainfo.txt         |    36 +
 doc/man/txt/xz.txt               |  1708 ++++++++++
 doc/man/txt/xzdec.txt            |    76 +
 doc/man/txt/xzdiff.txt           |    39 +
 doc/man/txt/xzgrep.txt           |    70 +
 doc/man/txt/xzless.txt           |    36 +
 doc/man/txt/xzmore.txt           |    31 +
 lib/Makefile.in                  |   623 ++++
 m4/.gitignore                    |    40 -
 m4/build-to-host.m4              |   274 ++
 m4/gettext.m4                    |   392 +++
 m4/host-cpu-c-abi.m4             |   529 +++
 m4/iconv.m4                      |   324 ++
 m4/intlmacosx.m4                 |    71 +
 m4/lib-ld.m4                     |   170 +
 m4/lib-link.m4                   |   815 +++++
 m4/lib-prefix.m4                 |   334 ++
 m4/libtool.m4                    |  8488 +++++++++++++++++++++
 m4/ltoptions.m4                  |   467 +++
 m4/ltsugar.m4                    |   124 +
 m4/ltversion.m4                  |    24 +
 m4/lt~obsolete.m4                |    99 +
 m4/nls.m4                        |    33 +
 m4/po.m4                         |   456 +++
 m4/progtest.m4                   |    92 +
 po/.gitignore                    |    31 -
 po/Makefile.in.in                |   517 +++
 po/Rules-quot                    |    66 +
 po/boldquot.sed                  |    21 +
 po/ca.gmo                        |   Bin 0 -> 15587 bytes
 po/cs.gmo                        |   Bin 0 -> 7983 bytes
 po/da.gmo                        |   Bin 0 -> 9040 bytes
 po/de.gmo                        |   Bin 0 -> 29882 bytes
 po/en@boldquot.header            |    35 +
 po/en@quot.header                |    32 +
 po/eo.gmo                        |   Bin 0 -> 15060 bytes
 po/es.gmo                        |   Bin 0 -> 29228 bytes
 po/fi.gmo                        |   Bin 0 -> 28225 bytes
 po/fr.gmo                        |   Bin 0 -> 10232 bytes

Чтобы можно было легко изучить, что конкретно поменялось в tarball релиза по сравнению с содержимым тегов релиза git, лучше всего использовать инструмент Meld, вызываемый через git difftool --dir-diff fa1e8796^..fa1e8796.

Meld invoked by git difftool --dir-diff afba662b..fa1e8796 to show differences between git release tag and release tarball contents

Для сравнения изменений между новым и старым tarball апстрима необходимо сравнить коммиты afba662b New upstream version 5.8.0 и fa1e8796 New upstream version 5.8.1, выполнив git difftool --dir-diff afba662b..fa1e8796.

Meld invoked by git difftool --dir-diff afba662b..fa1e8796 to show differences between to upstream release tarball contents

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

Нужно ли было выявить бэкдор XZ при помощи этих инструментов?

Знаменитый бэкдор XZ Utils (CVE-2024-3094) состоял из двух частей: самого бэкдора, находящегося внутри двух двоичных блобов, замаскированных под файлы тестов (tests/files/bad-3-corrupt_lzma2.xztests/files/good-large_compressed.lzma), и небольшого изменения в скриптах сборки (m4/build-to-host.m4), извлекающего бэкдор и инъецирующего его в в собранный двоичный файл. Скрипт сборки отслеживался в системе управления версиями, но генерировался при помощи GNU Autotools во время релиза и поставлялся только как дополнительные файлы в tarball релиза.

Причиной написания этого поста для меня стал вопрос: может ли усердный инженер, воспользовавшись best practices git-buildpackage, выявить это при импорте нового релиза апстрима в Debian. Если говорить вкратце, то нет. Злоумышленник в данном случае точно предвидел все стандартные способы исследования коммитов git и содержимого tarball релизов, и в течение долгого времени хорошо маскировал эти изменения.

Во-первых, в XZ есть вполне оправданные причины использования тщательно прописанных файлов .xz в качестве данных тестов, помогающих выявлять регрессии в пути выполнения кода распаковки. Файлы тестов поставляются в релизе, чтобы пользователи могли запускать тестовые наборы и валидировать правильность сборки двоичного файла и работы xz. Известно, что Debian выполняет большой объём тестирования в CI и системе autopkgtest для десятков тысяч пакетов для поддержания высокого качества при частых обновлениях тулчейна сборки и совместимости с бóльшим количеством архитектур CPU, чем любой другой дистрибутив. Данные тестов полезны, поэтому должны остаться в релизе.

При корректном использовании git-buildpackage коммиты апстрима видны в пакете Debian для удобства проверки, но коммит cf44e4b, добавивший файлы тестов, не особо отличается от неаккуратных практик кодинга. К сожалению, в коммитах git часто отсутствует тело сообщения, объясняющее причину внесения изменения, и часто код теста и данные теста не находятся в одном коммите, не обеспечивая атомарность; к тому же коммиты часто пушатся непосредственно в основную ветвь без использования ревью кода (в данном случае коммит не относился ни к одному из PR). Только другой разработчик апстрима мог бы заметить, что это изменение не соответствует тому, что ожидает проект, и что добавлены были только данные тестов без кода теста, а значит, этот коммит был не небрежным, а потенциально злоумышленным.

Во-вторых, появление нового файла Autotools (m4/build-to-host.m4) в XZ Utils 5.6.0 тоже не было чем-то подозрительным. Это совершенно нормально для Autotools. На самом деле, начиная с XZ Utils версии 5.8.1 поставляется реально используемый файл m4/build-to-host.m4.

Обнаружить, что здесь происходит что-то подозрительное, практически невозможно при изучении кода, так как файлы Autotools — это полностью собственный синтаксис m4, переплетённый со скриптом оболочки; к тому же там есть множество множество обратных штрихов (`), порождающих подоболочки, а также eval для работы с содержимым переменных. И всё это абсолютно нормально для AutotoolsВ посте Расса Кокса о XZ объясняется, как код Autotools получил сам бэкдор из файлов тестов и инъецировал его в сборку.

Inspecting the m4/build-to-host.m4 changes in Meld launched via git difftool

Есть только один крошечный аспект, который потенциально мог заметить только очень опытный пользователь Autotools: значение serial 30 в заголовке версии слишком высокое. Теоретически, можно было также заметить, что этот файл Autotools отличается от того, что поставляют другие пакеты в Debian с тем же именем файла, например, от версий serial 3, serial 5a и 5b. Однако для этого бы понадобился невероятный объём дополнительной работы по проверке, а мы не планируем начинать это практиковать. Гораздо проще было бы просто настойчиво рекомендовать всем опенсорсным проектам перестать использовать Autotools и постепенно полностью от него отказаться.

Бэкдор нельзя было выявить при разумном уровне трудозатрат

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

Если бы бэкдор не замедлил SSH примерно на 500 миллисекунд (из-за чего его и обнаружили), то, вероятно, он оставался бы незамеченным в течение нескольких месяцев или лет. Прятать бэкдоры в ПО с закрытыми исходниками относительно просто, а скрывать их на ровном месте в популярном оперсорсном проекте требует необычно высокого уровня опыта и изобретательности, что мы и показали выше.

Легко ли проводить аудит цепочки поставок в Debian?

При мейнтенинге исходников пакетов Debian с использованием git-buildpackage исследование истории пакетов становится намного проще; у большинства пакетов в debian/gbp.conf указаны неполные конфигурации, поэтому их истории разработки пакетов не всегда сконструированы корректно и однородно. Политика Debian вообще не требует применения git, и многие важные пакеты его не используют. Кроме того, политика Debian позволяет пользователям, не являющимся мейнтейнерами, загружать новые версии Debian без коммитов в git даже в случае пакетов, для которых исходный мейнтейнер хотел использовать git. Загрузки в обход git, к сожалению, случаются на удивление часто.

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

Сейчас Debian не хватает какого-то ПО, которое бы помогло выявлять подобные уязвимости?

Насколько я знаю, пока в инфраструктуре QA и безопасности Debian нет механизмов для верификации того, что пакеты исходников апстрима в Debian действительно поступают из апстрима. Я видел множество пакетов, в которых некорректны debian/watch или другие конфигурации, и даже встречал случаи, когда мейнтейнеры вручную создавали tarball апстрима, потому что это было проще, чем сконфигурировать автоматизацию. Очевидно, что для этих пакетов, tarball исходников которых уже находится в Debian, он совершенно отличается от tarball апстрима. Впрочем, о каких-то случаях злонамеренного использования этого мне неизвестно (а если бы было, я, разумеется, отправил бы о них отчёт).

Также мне известны пакеты в репозитории Debian, для которых ошибочно указан тип пакетов 1.0 (native), произошло смешение файлов апстрима и debian/contents и применены патчи, хотя на самом деле они должны быть сконфигурированы как 3.0 (quilt) и не прятать то, что является истинными исходниками апстрима. Проекту Debian нужно дополнить инструменты QA, чтобы обеспечить сканирование подобных вещей. Если я найду спонсора, то создам такую систему сам, как свой следующий крупный вклад в Debian.

Кроме улучшенного инструментария для поиска несоответствий в исходном коде, Debian бы пригодился более совершенный инструментарий для отслеживания в собранных двоичных файлах их исходников, однако решения наподобие supply-graph Fraunhofer-AISEC и ESSTRA компании Sony пока нельзя применять на практике. В посте Джулиена Малка о NixOS говорится о роли воспроизводимых сборок, которые могли бы в некоторых случаях помочь всем дистрибутивам.

Возможно, Debian не хватает каких-то политик или практик для решения подобных проблем?

Наверно, сообществу разработчиков Debian следовало бы перейти от мировоззрения «каждый может делать, что угодно» к тому, чтобы больше ценить более совместные рабочие процессы; это было бы даже полезнее, чем расширенное сканирование. Возможности проведения аудитов сильно мешает то, что одну и ту же задачу можно выполнять слишком большим количеством способов, поэтому очень сложно отличать «обычное» отклонение процесса от злонамеренного, потому что «обычным» может быть что угодно.

Кроме того, из-за отсутствия задокументированного и рекомендуемого «стандартного» процесса и новички, и ветераны в упаковке Debian могут так и не научиться какому-то одному оптимальному процессу, из-за чего будут выполнять на этапе упаковки множество как будто работающих, но на самом деле ошибочных или ненужных этапов, из-за чего отклонения от процесса могут показаться злонамеренными, но на самом деле окажутся результатом недостаточного понимания правильного способа выполнения задачи.

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

Я уже какое-то время продвигаю идею расширения использования запросов на слияние в Debian, например, предложив DEP-18: Encourage Continuous Integration and Merge Request based Collaboration for Debian packages. Если вы участвуете в разработке Debian и хотите, чтобы я продолжал их продвигать, то, пожалуйста, проголосуйте в dep-team/deps!21.

Можно ли доверять опенсорсному ПО?

Да — и я утверждаю, что можно доверять только опенсорсному ПО. Мы никак не можем выполнять аудит ПО с закрытыми исходниками, и пользователи, например, Windows или MacOS вынуждены доверять на слово поставщику в том, что в его ПО нет намеренных или случайных бэкдоров. А когда появляются новости, что системы поставщика ПО с закрытыми исходниками скомпрометированы, как это было с Crowdstrike несколько недель назад, мы не сможем провести какой-либо аудит и будем каждый раз вынуждены доверять ему в том, что он должным образом подчистил свою кодовую базу.

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

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

Комментарии (1)


  1. Yami-no-Ryuu
    23.10.2025 19:27

    Немного странная статья.

    Почти вся суть о контроле за мэйнтейнером. Но в качестве примера - скомпрометированный апстрим.

    Вывод в стиле ЧТД, это не отслеживаемо без понимания кода. В огороде бузина а в Киеве Отто.