Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

ЛентыКонтактыКонфиденциальность© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-31413-BPF-Container-Escape — CVE-2026-31413: ошибка корректности верификатора BPF — побег из контейнера | Kitploit
Инструменты/GitHubGitHub/rat5ak/cve-2026-31413-bpf-container-escape
Повышение привилегийАнализ уязвимостейЭксплуатацияПост-эксплуатацияСтатьи и ИсследованияОбучение и ОбразованиеПобег из КонтейнераЭксплуатация Бинарных Файлов

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
GitHub
rat5ak/cve-2026-31413-bpf-container-escape

CVE-2026-31413-BPF-Container-Escape

CVE-2026-31413: ошибка корректности верификатора BPF — побег из контейнера

Репозиторий
11296 месяцев назадЕщё не проверено

CVE-2026-31413: Один байт в BPF-верификаторе — и побег из контейнера

Я нашёл ошибку корректности (soundness) в Linux BPF-верификаторе — + 1 в вызове push_stack(), из-за которого верификатор пропускает ALU-инструкцию на ответвлённом пути. Для BPF_OR это означает, что верификатор отслеживает dst = 0, тогда как CPU вычисляет 0 | K = K. Я написал полноценный побег из контейнера: OOB-чтение/запись из BPF-карты, перехват vtable, перезапись modprobe_path, root на хосте. Затем я подготовил серию из двух патчей — исправление верификатора в один символ и 90 строк selftests — и добился включения её в mainline.

📹 Видео-демонстрация побега из контейнера

CVECVE-2026-31413
Класс ошибкиКорректность верификатора — расхождение значений регистров
Корневая причинаpush_stack(env, env->insn_idx + 1, ...) пропускает ALU-инструкцию на ответвлённом пути
Внесеноbffacdb80b93 — Linux 7.0-rc1 (14 января 2026)
Исправленоc845894ebd6f — Linux 7.0-rc5 (22 марта 2026)
Затронутые версии6.12.75+ (стабильный бэкпорт dea9989a3f) вплоть до 7.0-rc4
ВоздействиеПроизвольное чтение/запись в ядре → побег из контейнера → root на хосте
ТребуетсяCAP_BPF + CAP_PERFMON + CAP_NET_ADMIN
ИсправлениеОдин символ: insn_idx + 1 → insn_idx

TL;DR

maybe_fork_scalars() разветвляет состояние верификатора, когда видит ARSH + AND/OR с константой. Ответвлённый путь получает dst = 0 и пропускает ALU-инструкцию. Для AND это нормально: 0 & K = 0. Для OR это неверно: 0 | K = K, а не 0.

Верификатор считает регистр нулевым. В CPU — K. Я использовал это, чтобы построить произвольное OOB-чтение/запись из значения BPF-карты, получить адрес карты в ядре, собрать поддельную vtable bpf_map_ops, перенаправить map_push_elem через array_map_get_next_key для произвольной записи и перезаписать modprobe_path. Стоит запустить неизвестный бинарный формат — и ядро выполняет мой скрипт от root. Для контейнера это полный побег на хост.

Серия из двух патчей: исправление верификатора в один символ плюс 90 строк BPF-selftests, покрывающих разветвление OR и AND. Влито Алексеем Старовойтовым 22 марта. CVE-2026-31413 присвоен Грегом Кроа-Хартманом 12 апреля.


Предыстория: BPF-верификатор

eBPF позволяет загружать небольшие программы в ядро — фильтры пакетов, перехватчики трассировки, политики безопасности — без компиляции модуля ядра. Загвоздка в том, что вы внедряете код в кольцо 0 (ring 0). Если в этом коде есть ошибка, это ошибка ядра.

Поэтому перед запуском любой BPF-программы верификатор ядра симулирует каждый возможный путь исполнения. Он отслеживает, что находится в каждом регистре (указатель? скаляр? какой диапазон?), проверяет каждый доступ к памяти на соответствие границам карты и отклоняет всё, что может читать или записывать за пределами границ. Если верификатор говорит, что программа безопасна, JIT компилирует её в нативный машинный код и выполняет с полными привилегиями ядра. После этого проверок границ во время выполнения нет. Верификатор и есть граница безопасности.

Именно поэтому ошибки корректности верификатора отличаются от обычного повреждения памяти. При переполнении кучи или UAF вы получаете один примитив повреждения и дальше работаете от него — распыляете кучу, выстраиваете объекты, ловите окно гонки. При ошибке верификатора вы заставляете ядро поверить лжи о значении регистра. Все проверки границ, зависящие от этого регистра, проходят. Ядро одобрило ваш OOB-доступ и выполняет его без вопросов. Если правильно выстроить состояние регистров, из этого получается чистый и надёжный примитив.

Как я это нашёл

Я аудировал maybe_fork_scalars() — новый код, добавленный в январе 2026 года в bffacdb80b93. Разветвление состояния всегда интересно, потому что именно здесь верификатор расходится на параллельные пути исследования, и если какой-то путь отслеживает неверное значение, всё ниже по этому пути становится некорректным (unsound).

Функция разветвляет состояние, когда видит ARSH + AND/OR с константным источником. Ответвлённый путь получает dst = 0 и пропускает ALU-инструкцию. Я читал строку push_stack(env, env->insn_idx + 1, ...) — и меня тут же осенило: + 1 означает, что ответвлённый путь никогда не выполняет ALU-операцию. Для AND 0 & K = 0, так что пропуск допустим. Для OR 0 | K = K. Ответвлённый путь считает результат равным 0, хотя на самом деле это K.

В тот же вечер я написал BPF-программу. ARSH 63, чтобы получить {0, -1}, OR с константой, условная ветка для разделения путей верификатора, и затем сложение «нулевого» регистра с указателем на карту. Верификатор одобрил map_value + 0. CPU обратился к map_value + K. KASAN подтвердил выход за границы при тестировании.

OOB-чтение/запись — к следующему утру. Побег из контейнера — к следующей ночи. На протяжении всей работы я использовал Claude (Opus 4.5) — и для разбора логики разветвления состояния верификатора, и для мозгового штурма примитивов эксплуатации, и для превращения OOB в полную цепочку побега. Подход с перехватом vtable родился в ходе диалога, в котором Claude прошёлся по указателям на функции bpf_map_ops в поисках вызываемых гаджетов.

Коммит, внёсший ошибку

Коммит bffacdb80b93 («bpf: Recognize special arithmetic shift in the verifier») попал в 7.0-rc1 14 января 2026 года. Автор — Алексей Старовойтов, соавтор — Puranjay Mohan. В нём была добавлена maybe_fork_scalars() для обработки паттерна LLVM DAGCombiner:``` w2 s>>= 31 // arithmetic shift right: w2 becomes 0 or -1 w2 &= -134 // AND with constant K

LLVM преобразует `select_cc setlt X, 0, A, 0` в `sra + and`. После арифметического сдвига вправо регистр равен либо `0` (неотрицательный вход), либо `-1` (все единицы). AND с константой даёт `0` или `K`.

Верификатор не может отслеживать `{0, K}` в одном `bpf_reg_state` — его диапазон со знаком `[0, K]` является завышенным приближением, и из-за этого он отклонял корректные программы Cilium. Исправление: разветвить состояние верификатора. Один путь исследует `dst = 0`, другой — `dst = -1`, каждый отслеживая точное значение.

Реализация:```c
static int maybe_fork_scalars(struct bpf_verifier_env *env,
                              struct bpf_insn *insn,
                              struct bpf_reg_state *dst_reg)
{
    // ... condition check: dst range is [-1, 0], src is constant ...

    branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false);
    //                             ^^^^^^^^^^^^
    //                    pushed path resumes AFTER the ALU insn
    if (IS_ERR(branch))
        return PTR_ERR(branch);

    regs = branch->frame[branch->curframe]->regs;
    __mark_reg_known(&regs[insn->dst_reg], 0);   // pushed: dst = 0
    __mark_reg_known(dst_reg, -1ull);             // current: dst = -1
    return 0;
}

На пути, который был помещён в стек, происходят две вещи:

  1. Регистр назначения устанавливается в 0
  2. Выполнение возобновляется с insn_idx + 1 - инструкции после операции ALU

Для BPF_AND: dst = 0, AND пропускается. Во время выполнения: 0 & K = 0. Совпадает. Корректно.

Для BPF_OR: dst = 0, OR пропускается. Во время выполнения: 0 | K = K. Несовпадение. Верификатор видит 0. Процессор имеет K. Некорректно.

Функция не проверяет код операции. Она была написана для AND - где пропуск инструкции эквивалентен её выполнению с dst = 0 - и была применена также к OR. Для OR эта эквивалентность не выполняется.

Инициирование расхождения

Скачать инструмент