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

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

version normalization

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

Матчинг на уязвимости — это не сравнение строк, а проверка попадания версии в уязвимый диапазон CVE (versionStartIncluding, versionEndExcluding). Но «версия» в C/C++ записывается десятком несовместимых способов: upstream-тег 3.0.11, debian-версия 3.0.11-1~deb12u2 с epoch и ревизией, RPM-релиз 3.0.11-2.el9, строка из бинаря OpenSSL 3.0.11 19 Sep 2023, suffix 1.2.13.dfsg. Если в SBOM попала дистрибутивная строка целиком, а CVE оперирует upstream-версией, сравнение версий ломается — и снова не как ошибка, а как тишина в findings.

C/C++ особенно уязвим, потому что одна и та же библиотека приходит из разных миров с разными схемами версий: системный apt/rpm, Conan, ручной third_party/. Нормализация версии — это приведение к той форме, в которой её сравнивает целевая база уязвимостей, с сохранением исходной строки для трассировки.

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

Дистрибутивная версия несёт лишние компоненты, которых нет в CVE-диапазоне:

upstream (как в CVE):   3.0.11
deb:                    3.0.11-1~deb12u2        epoch?  rev=-1  +~deb12u2
rpm:                    3.0.11-2.el9            release=-2.el9
из бинаря:              "OpenSSL 3.0.11 19 Sep 2023"
+dfsg/+ds:              1.2.13.dfsg-1           +dfsg — переупаковка, не апстрим-версия
epoch:                  1:1.2.13.dfsg-1         ведущее "1:" — debian epoch

Сравнение версий по-разному ведёт себя в разных схемах: для deb это dpkg --compare-versions, для rpm — rpmdev-vercmp, для упорядоченных по компонентам — semver. Поэтому одну и ту же версию нельзя сравнивать «как строку»:

$ dpkg --compare-versions "3.0.11-1~deb12u2" lt "3.0.11" && echo "tilde < release"
tilde < release          # ~ делает версию МЕНЬШЕ базовой — частый источник ошибок

Для матчинга на NVD из 3.0.11-1~deb12u2 нужно извлечь upstream-ядро 3.0.11; ревизию -1~deb12u2 и epoch сохранить отдельно — они различают сборку, но не входят в CVE-диапазон upstream.

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

Слепая зона — сравнивать версии лексикографически и терять смысл суффиксов. 3.0.11-1~deb12u2 строкой «больше» 3.0.11, а по правилам Debian — меньше; 1.2.13.dfsg — это та же upstream 1.2.13, переупакованная. Приём: знать схему версии источника (deb/rpm/semver) и сравнивать её родными правилами, отделяя upstream-ядро (для CVE-диапазона) от distro-метаданных (для трассировки сборки). Не выкидывайте epoch и ревизию — сохраняйте их рядом.

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

  1. Для каждого компонента фиксировать схему версии источника: deb, rpm, conan/semver, строка из бинаря.
  2. Выделять upstream-ядро версии для матчинга на upstream-CVE; epoch/ревизию/+dfsg хранить отдельно как distro-метаданные.
  3. Сравнивать версии родными правилами: dpkg --compare-versions для deb, rpm-сравнение для rpm, semver — для упорядоченных.
  4. Сверять не точную строку, а попадание в уязвимый диапазон CVE (versionStartIncluding/versionEndExcluding).
  5. Сохранять исходную строку версии в SBOM как evidence — не затирать её нормализованной.
  6. Отдельно проверять версии, восстановленные из бинаря: формат там свободный и легко искажается.

Связь