Когда в upstream-ядре закрывают use-after-free или переполнение буфера, исправление попадает в mainline за дни. А вот в вашем RHEL 8 с ядром 4.18 или Ubuntu 22.04 LTS с 5.15 этот патч может застрять на недели - или прийти кастрированным. Я видел ситуации, когда вендор формально закрывал CVE, а attack surface оставался нетронутым. Автоматизированное обнаружение CVE в ядре Linux на уровне backport-патчей - единственный способ не полагаться на слепое доверие к вендору дистрибутива. Покажу конкретные техники и инструменты, которыми пользуюсь в ежедневной работе с downstream-деревьями, объясню, почему стандартный
grep по changelog бесполезен, и дам пошаговый алгоритм для вашего собственного pipeline.Почему backport-патчи - слепая зона безопасности ядра
Модель разработки ядра Linux устроена так: баг фиксится в mainline (ветка Линуса), затем мейнтейнеры stable-веток (Грег Кроа-Хартман и команда) cherry-pick'ают коммит в 6.6.x, 6.1.x и т.д. Дальше вендоры дистрибутивов забирают эти фиксы и адаптируют под свои ядра, которые могут отличаться от upstream на тысячи патчей.На каждом этапе что-то ломается:
- Неполный cherry-pick. Upstream-фикс состоит из трёх коммитов, в stable попали два. Третий «зависит от рефакторинга, которого нет в старой ветке». В итоге CVE формально закрыт, но attack surface остаётся.
- Конфликт при адаптации. Backport требует ручного разрешения merge-конфликтов. Разработчик может неверно адаптировать логику - патч компилируется, проходит тесты, но дыру не затыкает.
- Задержка. По данным исследователей FixMorph (ACM ISSTA 2021), около 8% всех коммитов в mainline бэкпортируются в старые stable-ветки, но между исходным фиксом и появлением backport проходит больше месяца. В этом окне система голая.
Ссылка скрыта от гостей
- use-after-free в nf_tables (CWE-416, CVSS 7.8 HIGH, вектор CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H). Уязвимость в nft_verdict_init(), которая допускала положительные значения как drop error, приводя к double-free в nf_hook_slow(). Исправление появилось в mainline коммитом f342de4e2f33e0e39165d8639387aa6c19dff660, затем перенесено в stable-ветки 5.15.149, 6.1.76 и 6.6.15. Но между upstream-фиксом и backport в 5.15 прошло несколько недель - а рабочий эксплоит с 99.4% успешностью уже был публичен.С точки зрения MITRE ATT&CK такие задержки - прямой путь к
Ссылка скрыта от гостей
(T1068): атакующий берёт известную kernel-уязвимость и поднимает привилегии, пока downstream-дистрибутив ждёт backport.Почему grep по CVE-номеру в changelog недостаточен
Первое, что делает начинающий инженер - запускаетgrep CVE-2024-1086 /usr/share/doc/linux-image-[I]/changelog[/I]. Иногда это работает. Чаще - нет.Коммиты без CVE в сообщении
Ядро Linux стало собственной CNA (CVE Numbering Authority) только в феврале 2024 года. До этого момента огромная часть security-фиксов попадала в stable без какого-либо упоминания CVE. Коммит выглядел как «nf_tables: fix verdict handling in nft_verdict_init()» - и всё. Идентификатор присваивался позже, иногда через месяцы, когда исследователь или NVD-аналитик связывал коммит с уязвимостью.После создания kernel CNA картина изменилась: ежедневно присваиваются десятки CVE-идентификаторов коммитам, которые раньше никто не считал security-фиксами. Прозрачности прибавилось, но появилась другая головная боль - лавина CVE, в которой нужно быстро определять, какие уже закрыты вашим downstream-ядром.
Backport меняет commit hash
git cherry-pick создаёт новый коммит с новым SHA. Upstream-коммит f342de4e2f33 в вашем downstream-дереве будет иметь совершенно другой хэш. Если вендор не добавил строку (cherry picked from commit f342de4e...) в commit message - связь теряется.Патч адаптирован, а не скопирован
При backport меняются контекстные строки, имена переменных (из-за отсутствия промежуточных рефакторингов), пути к файлам. Чистыйdiff между upstream-патчем и downstream-версией покажет расхождения, даже если логика идентична.Вывод: нужна автоматизация, которая работает на уровне семантики кода, а не текстового поиска.
Linux backport уязвимости: техники автоматического обнаружения
Разберём основные подходы - от простых git-команд до LLM-агентов.Метод 1: git patch-id для точного сопоставления
git patch-id вычисляет хэш содержимого патча, игнорируя контекстные строки, номера строк и whitespace. Это позволяет сопоставить upstream-коммит с cherry-pick'нутым downstream-коммитом, даже если SHA отличается.
Bash:
# Получаем patch-id upstream-коммита (фикс CVE-2024-1086)
git -C linux-upstream show f342de4e2f33 | git patch-id --stable
# Результат: <patch-id-hash> f342de4e2f33e0e39165d8639387aa6c19dff660
# Получаем patch-id всех коммитов в downstream-ветке за период
git -C linux-downstream log --format="%H" v5.15.148..v5.15.149 | \
while read commit; do
git show "$commit" | git patch-id --stable
done | grep "<patch-id-hash>"
Ограничение: если backport потребовал адаптации (changed context, renamed symbols), patch-id будет другим. Тут нужны инструменты посерьёзнее.
Метод 2: kernel-backport-checker - автоматизация поиска CVE через git
kernel-backport-checker - command-line утилита, которая определяет, какие CVE закрыты backport-коммитами в конкретном git-репозитории, фильтруя по версии ядра.Принцип работы:
- Загружает маппинг CVE-to-commit из публичных баз (NVD, kernel.org security advisories)
- Для каждого CVE находит upstream-коммит(ы)
- Ищет соответствующие cherry-pick'и в целевом дереве через patch-id и commit message parsing
- Формирует отчёт: какие CVE закрыты, какие - нет
Bash:
# Пример использования (концептуальный - сверяйте с docs)
git clone https://github.com/hardenedlinux/kernel-backport-checker
cd kernel-backport-checker
# Проверка ветки 5.15.y на наличие backport для конкретных CVE
./check_backports.py \
--repo /path/to/linux-stable \
--branch linux-5.15.y \
--kernel-version 5.15.148 \
--cve-list CVE-2024-1086,CVE-2024-26592,CVE-2024-26594
Метод 3:
Ссылка скрыта от гостей
Coccinelle (spatch) - инструмент семантического патчинга для C-кода. Позволяет писать «семантические шаблоны» (SmPL) для поиска паттернов в коде ядра. Лично я использую его, когда нужно проверить, применена ли конкретная логика фикса, а не конкретный текст патча.Пример: CVE-2024-1086 исправлялась добавлением проверки на положительные значения в
nft_verdict_init(). Пишем SmPL-правило для детектирования отсутствия этой проверки:
C:
// detect_cve_2024_1086.cocci
// Ищем nft_verdict_init без проверки на NF_DROP с положительным error
@@
expression E;
@@
nft_verdict_init(...)
{
...
- // Отсутствие проверки: data->verdict.code & NF_VERDICT_MASK
+ // После фикса здесь должна быть валидация verdict code
...
}
Bash:
spatch --sp-file detect_cve_2024_1086.cocci \
--dir /path/to/kernel-source/net/netfilter/ \
--include-headers
Метод 4: cve-bin-tool для бинарного анализа
Когда доступа к исходникам downstream-ядра нет (проприетарный embedded-дистрибутив, appliance-прошивка), бинарный анализ - единственный вариант.cve-bin-tool от Intel проверяет бинарные файлы на наличие известных уязвимых версий библиотек и компонентов:
Bash:
pip install cve-bin-tool
# Сканирование образа firmware
cve-bin-tool --sbom-type cyclonedx --sbom-file sbom.json /path/to/firmware/
# Фильтрация по ядерным CVE
cve-bin-tool /path/to/vmlinuz --product linux_kernel
Автоматическое создание backport-патчей: FixMorph и PortGPT
FixMorph: AST-трансформация для автоматического backporting
FixMorph, разработанный для автоматического переноса патчей из mainline в старые stable-ветки, работает в три этапа:FixMorph и PortGPT - инструменты для создания backport-патчей, а не для обнаружения незакрытых CVE. Они нужны на следующем этапе: когда pipeline из предыдущего раздела выявил отсутствующий backport и надо его сгенерировать.
- Извлечение синтаксических правок между исходной и пропатченной версией файла
- Локализация точки применения в целевой ветке через version control history и clone detection
- Адаптация трансформации через анализ alignment между AST mainline и target, включая namespace-адаптации и импорт отсутствующих зависимостей
Bash:
docker pull rshariffdeen/fixmorph:issta21
# Или сборка из исходников
git clone https://github.com/rshariffdeen/fixmorph
docker build -t rshariffdeen/fixmorph .
PortGPT: LLM-агент для backporting
Согласно исследованию на arxiv (2024), PortGPT - LLM-агент на базе GPT-4o, который автоматизирует backporting, имитируя рабочий процесс живого разработчика. Отличия от rule-based подходов:- Доступ к git history: агент самостоятельно исследует историю коммитов, чтобы понять, когда функция была переименована или перемещена
- Compiler feedback loop: если backport не компилируется, PortGPT анализирует ошибки и корректирует патч
- Per-hunk adaptation: каждый «кусок» (hunk) патча адаптируется отдельно с учётом контекста целевой ветки
Но есть нюанс. Как отмечает один из авторов исследования Чжаоян Ли: «В репозиториях с плохой или непоследовательной историей коммитов - например, с неполными сообщениями или squashed-коммитами - производительность PortGPT может снизиться из-за отсутствующей или вводящей в заблуждение контекстной информации». А в downstream-деревьях многих вендоров история коммитов куда грязнее, чем в mainline Linux.
CVE detection kernel: пошаговый практический pipeline
Вот конкретный алгоритм, которым я пользуюсь для проверки downstream-ядра на наличие незакрытых CVE. Подход комбинирует несколько методов, чтобы минимизировать false negatives.
📚 Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Этот пятишаговый pipeline - основа автоматизированного аудита безопасности ядра для downstream-дистрибутивов. Он ловит как точные cherry-pick'и, так и адаптированные backport'ы.
Анализ безопасности ядра Linux: реальные примеры незакрытых backport
Чтобы показать, зачем всё это нужно на практике, разберём несколько CVE из верифицированных данных NVD, где backport-процесс создаёт реальные дыры.ksmbd: каскад уязвимостей с разными сроками backport
Серия CVE в модуле ksmbd - типичная история: несколько связанных уязвимостей закрываются в разных stable-ветках в разное время.| CVE | Тип | CVSS | CWE | Исправлено в mainline | Backport в stable |
|---|---|---|---|---|---|
| CVE-2024-26592 | UAF (race condition) | 7.8 HIGH | CWE-416 | 6.8 | 6.1.75, 6.6.14, 6.7.2 |
| CVE-2023-52440 | Heap buffer overflow (slub) | 7.8 HIGH | CWE-119 | 6.5 | 5.15.145, 6.1.53, 6.4.16 (версии stable требуют верификации по git.kernel.org) |
| CVE-2024-26594 | OOB read | 7.1 HIGH | CWE-125 | 6.8 | 6.1.75, 6.6.14, 6.7.2 |
| CVE-2023-52442 | Пропуск проверки session/tree id (авторская оценка: CWE-863 Incorrect Authorization) | 5.5 MEDIUM | Нет данных | 6.5 | 5.15.x, 6.1.x, 6.4.x |
| CVE-2023-52441 | OOB access | 7.8 HIGH | CWE-119 | 6.5 | 5.15.145, 6.1.53, 6.4.16 |
Обратите внимание: CVE-2024-26592 (race condition в ksmbd_tcp_new_connection, CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) и CVE-2024-26594 (невалидированный mech token) попали в stable 6.1.75, а CVE-2023-52440 (переполнение буфера в ksmbd_decode_ntlmssp_auth_blob) - в 6.1.53, то есть намного раньше.
Без автоматизированного сканирования вы эту картину не увидите - вендорский changelog может не упоминать половину этих CVE по номерам.
USB-audio и UVC: уязвимости из каталога CISA KEV
CVE-2024-53150 (OOB read в USB-audio clock source parsing, CVSS 7.1 HIGH, CWE-125) и CVE-2024-53197 (OOB access в USB-audio для устройств Extigy и Mbox, CVSS 7.8 HIGH, CWE-787) были внесены CISA в каталог активно эксплуатируемых уязвимостей в апреле 2025 года. Обе связаны с тем, что bogus USB-устройство может предоставить некорректные значения bLength или bNumConfigurations, провоцируя out-of-bounds доступ.Эти уязвимости особенно интересны в контексте техники
Ссылка скрыта от гостей
(T1200, initial-access) при физическом доступе к машине - воткнул специально подготовленную флешку и атакуешь ядро напрямую.Автоматизированный аудит безопасности ядра: сборка CI/CD pipeline
Для интеграции проверок в CI/CD объединяем всё вышеописанное в единый скрипт:
Python:
#!/usr/bin/env python3
"""
kernel_cve_audit.py - автоматизированный аудит backport-патчей
Пример для демонстрации концепции
"""
import subprocess
import json
import sys
from pathlib import Path
class KernelCVEAuditor:
def __init__(self, upstream_repo, downstream_repo, branch):
self.upstream = Path(upstream_repo)
self.downstream = Path(downstream_repo)
self.branch = branch
self._downstream_index = None
def build_patchid_index(self):
"""Индексируем все patch-id в downstream (batch-режим)"""
# Batch: один pipe вместо N вызовов git show
log_proc = subprocess.Popen(
["git", "-C", str(self.downstream),
"log", "-p", self.branch],
stdout=subprocess.PIPE
)
pid_result = subprocess.run(
["git", "patch-id", "--stable"],
stdin=log_proc.stdout,
capture_output=True, text=True
)
log_proc.stdout.close()
log_proc.wait()
index = {}
for line in pid_result.stdout.strip().split('\n'):
if not line:
continue
parts = line.split()
if len(parts) >= 2:
patch_id, commit_hash = parts[0], parts[1]
index[patch_id] = commit_hash
self._downstream_index = index
return index
def check_cve(self, cve_id, upstream_commit):
"""Проверяем наличие backport для конкретного CVE"""
# Метод 1: patch-id matching
show = subprocess.run(
["git", "-C", str(self.upstream),
"show", upstream_commit],
capture_output=True, text=True
)
pid = subprocess.run(
["git", "patch-id", "--stable"],
input=show.stdout,
capture_output=True, text=True
)
if pid.stdout.strip():
patch_id = pid.stdout.strip().split()[0]
if patch_id in self._downstream_index:
return {
"cve": cve_id,
"status": "FIXED",
"method": "patch-id exact match",
"downstream_commit":
self._downstream_index[patch_id]
}
# Метод 2: поиск cherry-pick reference
result = subprocess.run(
["git", "-C", str(self.downstream), "log",
"--grep", f"cherry picked from commit {upstream_commit[:12]}",
"--oneline", self.branch],
capture_output=True, text=True
)
if result.stdout.strip():
return {
"cve": cve_id,
"status": "FIXED",
"method": "cherry-pick reference",
"downstream_commit":
result.stdout.strip().split()[0]
}
# Метод 3: поиск по затронутым файлам
# (требует дополнительной верификации)
changed_files = subprocess.run(
["git", "-C", str(self.upstream),
"diff-tree", "--no-commit-id", "--name-only",
"-r", upstream_commit],
capture_output=True, text=True
)
return {
"cve": cve_id,
"status": "NOT_FOUND",
"method": "all methods exhausted",
"affected_files":
changed_files.stdout.strip().split('\n'),
"action": "MANUAL_REVIEW_REQUIRED"
}
if __name__ == "__main__":
auditor = KernelCVEAuditor(
upstream_repo="/src/linux-upstream",
downstream_repo="/src/linux-downstream",
branch="ubuntu/focal"
)
print("[*] Building patch-id index...")
auditor.build_patchid_index()
cves_to_check = [
("CVE-2024-1086", "f342de4e2f33e0e39165d8639387aa6c19dff660"),
# Добавьте другие CVE и коммиты
]
results = []
for cve_id, commit in cves_to_check:
result = auditor.check_cve(cve_id, commit)
results.append(result)
status_marker = "+" if result["status"] == "FIXED" else "!"
print(f"[{status_marker}] {result['cve']}: "
f"{result['status']} ({result['method']})")
# Экспорт результатов
with open("audit_report.json", "w") as f:
json.dump(results, f, indent=2)
Сравнение инструментов для kernel CVE сканирования
| Инструмент | Подход | Точность | Язык/формат | Покрытие | Стоимость |
|---|---|---|---|---|---|
| git patch-id | Хэш содержимого | Высокая (только exact match) | CLI / bash | Только чистые cherry-pick | Бесплатно |
| kernel-backport-checker | patch-id + NVD mapping | Средняя | Python / CLI | Linux kernel CVE | Бесплатно |
| coccinelle (spatch) | Семантический анализ AST | Высокая | SmPL / C | Любой C-код ядра | Бесплатно |
| FixMorph | AST-трансформация | 75.1% на 350 патчах | C / Docker | Linux kernel (C) | Бесплатно |
| PortGPT | LLM-агент (GPT-4o) | 89.15% на 1815 случаях | Python / API | C, C++, Go | Требует API GPT-4o |
| cve-bin-tool | Бинарный анализ версий | Низкая (version-level) | Python / CLI | Широкое (не только ядро) | Бесплатно |
| CVE Scan | SBOM + kernel config | Средняя-Высокая | SaaS / On-premise | Yocto, Buildroot, Zephyr | Коммерческий |
Моя рекомендация - комбинация: patch-id для быстрого скрининга, coccinelle для верификации сложных случаев, и cve-bin-tool / CVE Scan для embedded-систем без доступа к исходникам.
Типичные ловушки при поиске уязвимостей в патчах ядра
Опыт работы с downstream-деревьями RHEL и Ubuntu LTS научил меня нескольким вещам, которые автоматика регулярно пропускает:Многокоммитный фикс. CVE закрывается серией из 3-5 коммитов. Вендор бэкпортирует 4 из 5 - patch-id для каждого совпадает, аудит показывает «FIXED». Но пятый коммит, с ключевой проверкой, отсутствует. Решение: парсить
Fixes: тег в upstream-коммитах и проверять всю цепочку.Backport вносит регрессию. Патч адаптирован, компилируется, но из-за отсутствия промежуточного рефакторинга работает криво. Coccinelle тут помогает частично - можно написать правило, проверяющее наличие конкретных guard conditions, но полная верификация требует runtime-тестирования (syzkaller, kselftest).
CVE присвоен задним числом. Коммит
abc123 попал в stable-5.15 полгода назад как «bugfix». Вчера ему присвоили CVE. Ваш аудит-скрипт его не проверяет, потому что на момент последнего запуска CVE не существовал. Решение: регулярное обновление CVE-to-commit маппинга и полное пересканирование.Заключение
Обнаружение CVE в ядре Linux на уровне backport-патчей - не разовая акция, а конвейер, который должен крутиться постоянно. Десятки CVE-идентификаторов от kernel CNA каждый день, задержки backport'ов в stable-ветках и неизбежные ошибки адаптации делают ручной аудит физически невозможным.Комбинация git patch-id для быстрого скрининга, coccinelle для семантической верификации и Python-скриптов для оркестрации NVD-фидов закрывает большинство сценариев. Для масштабных проектов стоит присмотреться к FixMorph (AST-based, 75.1% точность) или PortGPT (LLM-based, 89.15%) - особенно если ваша команда поддерживает собственный downstream-форк ядра.
Главное правило: никогда не доверяйте changelog. Проверяйте патчи на уровне кода - автоматически, регулярно и с понятным fallback на ручной анализ. Прогоните pipeline из шага 5 на своём ядре прямо сейчас - если в отчёте вылезет хоть один MISS с CVSS > 7.0, считайте, что статья себя окупила.
Последнее редактирование модератором: