Пиков / КАПО
Как читать авторский курс и нормативные материалы

Это учебные материалы, а не заключение о соответствии конкретного продукта. Организационные модели курса отделяйте от терминов стандарта: ППК в опубликованном проекте — машиночитаемый перечень, а живой реестр его версий является практикой сопровождения. Термин OSA в разных материалах имеет разный охват. Статус и обязательность нормативного документа проверяются отдельно; проект КАПО опубликован как проект первой редакции.

статическая линковка

Почему это важно для SCA/OSA

Статическая линковка — каноничная слепая зона C/C++ SCA. При связывании с архивом .a линкер копирует нужные объектные файлы библиотеки прямо внутрь итогового бинаря. После этого отдельной сущности «библиотека» не остаётся: нет файла .so, нет записи в динамической секции, нет пакета на хосте. Компонент физически присутствует в продукте, но из всех привычных следов исчезает — и автоматический скан готового бинаря его не покажет.

Для управления уязвимостями это худший случай: вшитая статически уязвимая библиотека даёт false negative при матчинге. Сканер честно сообщает «динамических зависимостей нет», а в бинаре при этом сидит, например, старая OpenSSL с известной CVE. Поэтому статическая линковка — главный аргумент в пользу того, что полнота состава достигается на этапе сборки, а не поздним сканом.

Как это проявляется

Один и тот же код, собранный динамически и статически, даёт разную видимость зависимости. В динамическом бинаре библиотека есть в DT_NEEDED; в статическом — её там нет вообще:

$ readelf -d ./app_dyn | grep NEEDED
 0x0...0001 (NEEDED) Shared library: [libssl.so.3]
 0x0...0001 (NEEDED) Shared library: [libcrypto.so.3]

$ readelf -d ./app_static | grep NEEDED
 0x0...0001 (NEEDED) Shared library: [libc.so.6]

OpenSSL «испарилась» из секции .dynamic — но код её внутри. Доказать присутствие можно лишь косвенно: по символам (nm -C найдёт SSL_*, если бинарь не stripped) или по строке версии в данных:

$ strings ./app_static | grep -iE 'OpenSSL [0-9]'
OpenSSL 3.0.11 19 Sep 2023

И это в лучшем случае. Строки версии вшивают не все библиотеки, а strip убирает символы (см. stripped binaries) — тогда не остаётся даже косвенного следа.

Поздний скан ≠ build-scan У статически слинкованного компонента нет ни soname, ни пакета, ни (в stripped-бинаре) символов. Версию приходится угадывать по уцелевшей строке — если она есть. Единственный надёжный источник состава здесь — журнал сборки и флаги линковки (-l:libssl.a, -Wl,-Bstatic, -static), которые видны только во время сборки.

Слепая зона или приём

Сама статическая линковка и есть слепая зона; приём — ловить её там, где она ещё видна по имени. В строке линковки -l:libcrypto.a или -Wl,-Bstatic -lfoo библиотека названа явно — это единственное место, где статический компонент фигурирует под своим именем и версией пакета. Поэтому build-inventory по логу сборки (разбор compile_commands.json и строк линкера, см. компиляция и линковка) — не дополнение, а обязательное условие: без него статика выпадает из SBOM молча.

Если бинарь уже собран, а лога нет, остаётся честный результат «компонент присутствует, версия из бинаря недоступна» — и пометка в реестре неопознанных, а не пустая строка в SBOM.

Что делать на проекте

  1. Считать каждый статически слинкованный компонент кандидатом в false negative и не полагаться на скан готового бинаря.
  2. Источник истины для статики — журнал сборки: искать в нём -l:lib*.a, -Wl,-Bstatic, -static, -static-libstdc++ (см. компиляция и линковка).
  3. Если лога нет — пробовать косвенные следы: nm -C (символы) и strings | grep -iE 'версия'; результат фиксировать с пониженным confidence.
  4. На stripped-бинаре, где nm пуст и строки версии нет, оформлять компонент как «присутствует, версия недоступна» — это корректный исход, а не провал.
  5. Любую вшитую библиотеку без подтверждённой версии заносить в реестр неопознанных по порядку работы с неопознанной библиотекой.
  6. Требовать от поставщиков SBOM, собранный на этапе сборки, а не реконструированный из бинаря, — для статики это критично.

Связь