
Воспроизводитель гонки и стресс-инструментарий для CVE-2026-52910 — use-after-free в ядре Linux в программах-селекторах cBPF для reuseport, с проверками dmesg и утечек.
Инструмент для воспроизведения гонки и стресс-тестирования CVE-2026-52910 — use-after-free (UAF) в обработке ядром Linux классических BPF-программ (cBPF) селектора reuseport, исправленной в upstream коммитом "bpf: Free reuseport cBPF prog after RCU grace period".
| CVE | CVE-2026-52910 |
| Тип | Use-after-free / чтение за границами (CWE-125), CVSS 3.1 7.8 HIGH AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Появилась в | v4.5 (вместе с поддержкой cBPF reuseport) |
| Исправлена в | 5.10.259, 5.15.210, 6.1.176, 6.6.143, 6.12.94, 6.18.36, 7.0.13 (stable); mainline v7.1 |
| Upstream splat | BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock (net/core/sock_reuseport.c:596) |
| Обнаружил | Eulgyu Kim |
[!WARNING] Это инструмент стресс-тестирования ядра. На уязвимом ядре он намеренно расширяет окно гонки use-after-free; попадание может привести к падению или повреждению системы. Запускайте его только на машинах, которыми вы владеете или на которых вам явно разрешено тестирование (тестовые ВМ, одноразовые CI-машины), никогда — на продакшн-системах.
SO_REUSEPORT позволяет множеству сокетов привязываться к одному UDP-порту; для каждого
входящего пакета ядро выбирает один сокет из группы в
reuseport_select_sock() (net/core/sock_reuseport.c). Группа может установить
программу-селектор — классическую BPF-программу, прикрепляемую через
setsockopt(SO_ATTACH_REUSEPORT_CBPF) — которая решает для каждого пакета, какой сокет
группы его получит. Программа выполняется в RX softirq (обработка приёма сетевых пакетов)
внутри критической секции чтения RCU.
Баг: когда программа заменяется или отсоединяется через setsockopt()
(reuseport_attach_prog() / reuseport_detach_prog()), старая cBPF-программа
освобождается немедленно функцией sk_reuseport_prog_free(), без ожидания
активных RCU-читателей. CPU, всё ещё выполняющий инструкции освобождённой программы,
читает освобождённую память vmalloc:
sequenceDiagram
autonumber
participant C as CPU0 — churner thread
participant K as setsockopt() path
participant R as CPU1 — RX softirq
R->>R: rcu_read_lock()
R->>R: prog = rcu_dereference(reuse->prog)
C->>K: setsockopt(SO_ATTACH_REUSEPORT_CBPF, progB)
K->>K: swap progA → progB
K->>K: sk_reuseport_prog_free(progA)
Note right of K: unfixed kernels: bpf_prog_free()<br/>runs NOW — no RCU grace period
R->>R: execute progA->insns (run_bpf_filter)
Note right of R: progA was already freed<br/>KASAN: vmalloc-out-of-bounds
Note over K: fix: call_rcu(sk_reuseport_prog_free_rcu) —<br/>free deferred by one RCU grace period
Путь селектора eBPF (SO_ATTACH_REUSEPORT_EBPF) не затронут: он
уже освобождает программы через отложенные стадии bpf_prog_put(). Исправление
применяет к пути cBPF тот же подход — один период отсрочки RCU перед освобождением
старой программы.
Отчёт KASAN из upstream (на отладочном ядре 7.0):
BUG: KASAN: vmalloc-out-of-bounds in reuseport_select_sock+0xedc/0x1220
Read of size 4 at addr ffffc9000051e004 by task slowme/10208
net/core/sock_reuseport.c:596
| Файл | Назначение |
|---|---|
reuseport_race_hammer.c | Воспроизведение: многопоточный hammer, который непрерывно меняет cBPF-селектор под полной UDP-нагрузкой, а затем проверяет целостность доставки. |
run_hammer.sh | Обёртка для одного запуска: повышает net.core.optmem_max, запускает hammer, затем проверяет dmesg на splat'ы, /proc/vmallocinfo на утечки аллокаций bpf_prog и, опционально, kmemleak. |
livepatch_cycle.sh | Применяет/откатывает livepatch с исправлением, пока работает hammer — ищет опасности жизненного цикла livepatch для исправления на основе call_rcu(). |
Makefile | Собирает hammer. |
.github/workflows/ci.yml | CI: сборка + shellcheck (без runtime-тестов ядра; см. CI). |
На тестовой машине с Linux:
$ make
$ sudo ./run_hammer.sh 600 # 10-minute run
...
== result: RC=0 (0 clean / 1 setup / 2 splat / 3 leak / 4 integrity) ==
Требования:
gcc и bash.dmesg, /proc/vmallocinfo, sysctl);
сам hammer работает без привилегий (upstream-воспроизведение запускалось от UID 1000).Ожидаемые результаты:
RC=2; иногда проверка целостности
hammer срабатывает первой → RC=4.RC=0, стабильное количество vmalloc-аллокаций bpf_prog.Окно гонки крошечное (освобождение против выполняющегося RX), поэтому один чистый запуск считается неубедительным. Для реального тестирования запускайте часами, например:
$ sudo ./run_hammer.sh 86400 512 8 16 4 127.0.0.1 0
reuseport_race_hammer$ ./reuseport_race_hammer [dur_sec] [insns] [nports] [nsocks] [nsenders] [ip] [ebpf]
| Аргумент | По умолчанию | Значение |
|---|---|---|
dur_sec | 300 (мин. 45) | общее время работы в секундах |
insns | 256 | количество инструкций-заполнителей в переключаемой cBPF-программе; чем больше программа, тем больше освобождаемая область для попадания. Если attach завершается с ENOMEM, увеличьте net.core.optmem_max (обёртка делает это за вас). |
nports | 4 (макс. 64) | группы reuseport (по одному UDP-порту на каждую, начиная с 21000) |
nsocks | 8 (макс. 512) | сокетов на группу |
nsenders | 4 (макс. 32) | потоков UDP-отправителей на группу |
ip | 127.0.0.1 | целевой адрес; используйте IP физического NIC, чтобы распределить RX softirq по CPU (RSS) |
ebpf | 0 | 1 = также переключать attach/detach SO_ATTACH_REUSEPORT_EBPF (требуется CAP_BPF/CAP_NET_ADMIN); этот путь не уязвим, это для сравнения/покрытия |
Каждая группа запускает один поток churner, который в тесном цикле меняет и отсоединяет
cBPF-селектор через setsockopt(), и потоки-отправители, заливающие группу
64-байтовыми UDP-датаграммами, пока приёмники считают доставку по сокетам.
Фазы запуска (T = dur_sec):
time ──────────────────────────────────────────────────────────────►
[0 ──────────── T-15s) [T-15s ── T-10s) [T-10s ─────────── T]
CHURN + FLOOD SETTLE MEASURE
churner swaps/detaches churn frozen, deterministic program
the selector prog at final program (selects the LAST
max rate under full attached socket): EVERY packet
UDP flood — THE (selects LAST must land on the LAST
race window open socket) socket; snapshot A →
run → snapshot B
Проверка целостности: во время фазы измерения финальная программа детерминированно выбирает последний сокет группы. Если пакеты, полученные группой в течение этого окна, не все попали на этот сокет, выбор сработал неправильно (возможный эффект UAF даже без KASAN) → код выхода 2.
Коды выхода hammer: 0 PASS · 1 ошибка настройки/выполнения · 2 WARN целостности.
run_hammer.sh — обёртка для одного запускаЗапускает hammer и добавляет проверки, которые делают одиночный запуск осмысленным: