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

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

динамическая линковка

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

Динамическая линковка — единственный слой состава C/C++, который остаётся читаемым в готовом бинаре без журнала сборки. Линкер не вшивает код библиотеки, а записывает в файл лишь её имя (soname), а реальное связывание выполняет загрузчик ld.so при старте процесса. Поэтому динамическую зависимость можно восстановить пост-фактум: имя .so лежит в бинаре, а на хосте оно резолвится в файл и далее — в системный пакет с версией. Это «лёгкая» часть инвентаризации, на которой держится автоматический скан.

Ровно поэтому здесь возникает ложное ощущение полноты: сканер уверенно перечисляет динамические библиотеки и молчит про всё остальное. Важно понимать границу — динамическая линковка показывает компонент, но не его точную версию, и не видит ничего, что вшито статически.

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

Резолвинг идёт по soname, а не по имени файла. В бинаре записан мажорный ABI (libssl.so.3), ld.so ищет совпадение через кэш и подставляет конкретный файл:

$ ldd ./app
        libssl.so.3 => /usr/lib/x86_64-linux-gnu/libssl.so.3 (0x00007f...)
        libz.so.1   => /usr/lib/x86_64-linux-gnu/libz.so.1   (0x00007f...)

$ ldconfig -p | grep libssl
        libssl.so.3 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libssl.so.3

Имя libssl.so.3 фиксирует только мажорную ABI-версию — «какая-то OpenSSL 3.x». Чтобы получить точную версию (3.0.11), нужно перейти от файла к пакету (dpkg -S / rpm -qf), а от пакета — к его версии. Этот переход «soname → файл → DEB/RPM-пакет → версия» и есть рабочая логика динамической инвентаризации.

soname задаёт совместимость, не версию Мажор в soname (.so.3) меняется при несовместимом изменении ABI, а патч-версии (3.0.11 → 3.0.13) живут под одним soname. Значит, по самому бинарю патч-версию узнать нельзя — её даёт только пакетный менеджер хоста, на котором собирали или будут запускать.

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

Слепая зона динамической линковки — RPATH/RUNPATH: бинарь может указывать загрузчику искать .so не в системных путях, а в каталоге поставки. Тогда libssl.so.3 по имени то же, но физически загрузится другая, vendored-копия с иной версией, чем системный libssl3. ldd покажет фактический путь и тем самым выдаст подмену; структура этих записей разбирается в ELF и разделы dynamic.

Приём — не доверять имени soname как версии. Имя даёт только идентичность компонента (мажорная ABI), точную версию подтверждает пакет хоста, а реальный файл — ldd/путь. Если путь ведёт не в системный каталог, компонент vendored и требует отдельной разметки.

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

  1. Снимать динамический состав надёжным способом — из секции .dynamic (readelf -d), не запуская бинарь; ldd использовать как уточнение путей (детали в ldd readelf objdump).
  2. Каждую .so доводить до версии: dpkg -S / rpm -qf → имя пакета → версия пакета, а не останавливаться на soname.
  3. Проверять фактический путь резолвинга: .so вне системных каталогов (/opt/app/lib, рядом с бинарём) — признак RPATH/vendored-копии.
  4. Фиксировать обе версии — soname (мажор ABI) и точную версию пакета: для матчинга на CVE нужны обе.
  5. libc/libstdc++ учитывать как системный runtime отдельно, не смешивая с прикладными компонентами.
  6. Любую .so, не привязанную к пакету, отправлять в реестр unresolved (см. unresolved libraries).

Связь