Шесть машинных инструкций. Окно гонки шириной в шесть инструкций - и Jaeyoung Chung из Seoul National University Computer Security Lab превращает непривилегированный процесс в root с надёжностью 99 из 100 попыток. Эксплойт опубликован на oss-sec, баг зарегистрирован как CVE-2026-46242 (Bad Epoll) - use-after-free в
fs/eventpoll.c, CVSS 7.8 HIGH, вектор AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, CWE-416. И вот что неприятно: баг сидит в подсистеме, которую нельзя отключить. На epoll завязан весь I/O-мультиплексинг GNU/Linux - nginx, Node.js, Android event loop. Эксплойт подан как 0-day в Google kernelCTF (программа с вознаграждением от $71 337 за kernel exploit). Ниже - root cause из патча, цепочка эксплуатации до ROP-chain с root shell, затронутые версии и проверка экспозиции.Зачем атакующему local privilege escalation в ядре Linux
Privilege escalation через уязвимость ядра - Exploitation for Privilege Escalation (T1068) по MITRE ATT&CK - один из самых ценных примитивов в цепочке атаки. Bad Epoll не даёт initial access: уязвимость локальная, нужно предварительное исполнение кода на целевом хосте. Но в этом и роль: конвертировать ограниченный foothold в полную компрометацию.
Место в kill chain:
- До Bad Epoll: атакующий получил local code execution - через trojanized npm/pip-пакет, украденный SSH-ключ, RCE в CI/CD-задаче, browser exploit в рендерере или shell в контейнере
- Bad Epoll (T1068): непривилегированный код -> root
- После Bad Epoll: persistence через kernel modules (T1547.006), rootkit (T1014), credential access, lateral movement по инфраструктуре
Три свойства делают Bad Epoll опаснее типичного linux kernel privilege escalation.
Нет kill switch. Epoll - базовая подсистема ядра. Copy Fail (CVE-2026-31431) можно нейтрализовать выгрузкой уязвимого модуля - с epoll такой фокус не пройдёт. На нём работает ОС, сетевые сервисы и браузеры. Единственный путь - патч.
Работает из Chrome renderer sandbox. По данным исследователя, Bad Epoll триггерится из-под renderer sandbox Chrome - того самого sandbox, который блокирует большинство kernel-багов. Связка renderer exploit + Bad Epoll даёт kernel code execution - аналог цепочки, продемонстрированной Project Zero.
Кандидат на rooting Android. Из примерно 130 уязвимостей, проэксплуатированных на Google kernelCTF, только около десяти пригодны для root Android. Bad Epoll - одна из них. На Pixel 10 (ядро v6.6+) UAF подтверждён, полный linux root exploit в разработке.
Подсистема epoll: почему lifetime объектов становится attack surface
Epoll - механизм масштабируемого I/O-мультиплексинга. Программа создаёт экземпляр черезepoll_create1(), добавляет наблюдаемые файловые дескрипторы через epoll_ctl(), ожидает событий через epoll_wait(). На epoll построены event loop'ы nginx, Node.js, Python asyncio, Go runtime, баз данных и Android.За простым API скрыта плотная сеть зависимостей между kernel-объектами. Экземпляр epoll - сам
struct file. Наблюдаемые дескрипторы тоже ссылаются на struct file - сокеты, pipe, eventfd, timerfd, другие epoll-экземпляры. Последний случай - epoll-watches-epoll - легитимная часть API, но она превращает управление lifetime в минное поле: один объект уведомлений зависит от другого, и оба могут быть закрыты конкурентно разными потоками.Ментальная модель тут такая: epoll - не только event API, а граф ссылок между kernel-объектами. Баг возникает, когда ребро в графе удаляется раньше, чем нужно, а другой поток считает граф валидным и продолжает по нему ходить. Классическая проблема lifetime management, которая в ядре превращается в эксплуатируемый примитив.
Root cause CVE-2026-46242: use-after-free в epoll при конкурентном закрытии
Уязвимый коммит -
58c9b016e128 от 8 апреля 2023 года - уменьшал contention на ep_mutex в подсистеме epoll. Этот же коммит ввёл две отдельные race condition в примерно 2500 строк кода eventpoll.Механика гонки
Описание NVD указывает на проблему с хирургической точностью:ep_remove() через ep_remove_file() очищал file->f_ep под file->f_lock, но продолжал использовать @file в последующей критической секции. Это создавало окно, в котором конкурентный __fput() мог наблюдать транзитный NULL, пропустить eventpoll_release_file() и перейти к f_op->release / file_free().Что происходит при конкурентном закрытии двух связанных epoll-дескрипторов:
- Thread A вызывает
ep_remove(). Подfile->f_lockочищаетfile->f_ep = NULL. - Thread B конкурентно вызывает
__fput()на том жеstruct file. Видитf_ep == NULL-> считает файл не связанным с epoll -> пропускаетeventpoll_release_file()-> вызываетf_op->release/file_free(). - Thread A продолжает работу:
is_file_epoll(),hlist_del_rcu()черезepi->fllink.pprev,spin_unlock()- всё на уже освобождённых объектах.
struct eventpollUAF: в сценарии epoll-watches-epollhlist_del_rcu()пишет через указатель в уже освобождённыйkmalloc-192struct fileUAF:struct fileиспользуетSLAB_TYPESAFE_BY_RCU(https://www.kernel.org/doc/html/latest/mm/slub.html) - слот может быть переиспользован аллокатором, пока функция ещё держит указатель на объект, который уже не "тот самый"
Фикс и родственная CVE-2026-43074
Исправление - коммитa6dc643c6931 от 24 апреля 2026 года - захватывает ссылку на @file через epi_fget() в начале ep_remove(), до того как очищается ассоциация:
C:
static void ep_remove(struct eventpoll *ep, struct epitem *epi)
{
struct file *file __free(fput) = NULL;
ep_unregister_pollwait(ep, epi);
if (unlikely(READ_ONCE(epi->dying)))
return;
file = epi_fget(epi); /* pin file ДО очистки f_ep */
if (!file)
return;
spin_lock(&file->f_lock);
ep_remove_file(ep, epi, file);
}
epi_fget() - вся суть патча. С ним struct file не может быть освобождён, пока ep_remove() не завершит работу. Концептуально просто - но в ядре «простая» фиксация ссылки требует аккуратного размещения: слишком поздно - гонка сохраняется; на неправильный объект - один класс UAF закрывается, другой остаётся; с нарушением lock ordering - deadlock.Тот же коммит 2023 года ввёл CVE-2026-43074 (CWE-401, CVSS 7.8) - ещё одну race в epoll. Её обнаружила AI-модель Anthropic Mythos, и это само по себе примечательно: race-баги считаются одним из самых трудных классов для автоматического поиска. Фикс CVE-2026-43074 откладывал освобождение
struct eventpoll через RCU grace period. Но UAF на struct file - Bad Epoll - остался: устранение одного симптома не закрыло всю lifetime-проблему в дизайне.Почему Mythos пропустила именно Bad Epoll? Исследователь называет две вероятные причины. Окно гонки - шесть инструкций; точный interleaving потоков сложно визуализировать даже человеку, глядя на исходник. И после фикса CVE-2026-43074 Bad Epoll не триггерит KASAN - основной runtime-детектор memory errors ядра. Без этого сигнала у модели могло не хватить уверенности для репорта. Первый патч мейнтейнеров тоже не полностью закрыл проблему - корректный фикс появился только через два месяца после исходного репорта.
Эксплуатация уязвимости ядра Linux: от UAF к root shell
Требования к окружению для воспроизведения:
- Ядро Linux 6.4 - 6.12.94 (или 6.6 - 6.6.143 для LTS-ветки) без фикса
a6dc643c6931 - Local code execution от непривилегированного пользователя
- Компилятор C++ (exploit написан на C++)
- Для безопасного тестирования: QEMU + pwndbg/gdb, 2+ GB RAM для VM
Высокоуровневая цепочка эксплуатации bad epoll exploit:
- Четыре epoll-объекта создаются и группируются в две пары epoll-watches-epoll. Одна пара - триггер гонки, другая - жертва.
- Конкурентное закрытие триггерной пары провоцирует close-vs-close race. Эксплойт расширяет окно через timer interrupt и запускает retry-цикл, который не крашит ядро при неудаче. Это принципиально - можно долбить гонку хоть тысячу раз без паники.
- 8-байтная UAF-запись в освобождённый
kmalloc-192черезhlist_del_rcu(), который пишет черезepi->fllink.pprev. - Pivot на
struct fileUAF - cross-cache attack даёт полный контроль над содержимым file-объекта. - Arbitrary kernel read через
/proc/self/fdinfo- danglingstruct file, подставленный под pipe, утекает kernel-адреса. Обход KASLR без отдельного info leak. - Hijack control flow - перехват indirect call, захват instruction pointer.
- ROP-chain -> elevated privileges -> root shell.
- lts-6.12.67 - 99% reliability
- cos-121-18867.294.100 (Container-Optimized OS) - 98% reliability
- Pixel 10 (ядро v6.6+) - UAF подтверждён, полный root exploit в разработке. Pixel 8 и устройства на ядре v6.1 не затронуты.
Ограничения эксплуатации
Не remote: обязательно предварительное local code execution. Без foothold на хосте Bad Epoll бесполезен.Kernel CFI (
CONFIG_CFI_CLANG): усложняет ROP, но не блокирует UAF-примитив. Сам use-after-free и arbitrary read работают независимо от CFI.seccomp с жёстким whitelist: теоретически может блокировать epoll-syscalls, на практике epoll разрешён почти повсеместно - слишком много софта от него зависит.
grsecurity/PaX (RANDKSTACK и др.): усложняет heap spray и предсказание layout, не устраняет root cause. Затрудняет эксплуатацию, но не делает её невозможной.
Пропатченные ядра: 6.6.144+ и 6.12.95+ содержат фикс - exploit не работает.
Затронутые версии и проверка экспозиции Linux kernel
По данным OSV.dev и upstream-фиксов:| Ветка ядра | Статус |
|---|---|
| до 6.4 | Не затронута |
| 6.4 - 6.6.143 | Затронута, фикс в 6.6.144 |
| 6.7 - 6.12.94 | Затронута, фикс в 6.12.95 |
| 6.13+ | Затронута, проверяйте backport |
Enterprise-дистрибутивы бэкпортируют код, и
uname -r не отражает реальный patch status. Проверяйте по advisory, а не по major/minor:| Дистрибутив | Advisory | Версия с фиксом |
|---|---|---|
| Debian trixie | DSA-6381-1 (05.07.2026) | linux 6.12.95-1 |
| RHEL 10 | RHSA-2026:36541 (08.07.2026) | kernel-0:6.12.0-211.31.1.el10_2 |
| RHEL 9 | RHSA-2026:36645 (08.07.2026) | kernel-0:5.14.0-687.23.1.el9_8 |
| Azure Linux 3 | MSRC (31.05.2026) | azl3 kernel 6.6.143.1-1 |
Red Hat классифицирует RHEL 6 и RHEL 7 как "Not affected" - ядра этих версий основаны на ветках до 6.4.
Bash:
# Текущая версия ядра
uname -r
# RHEL/CentOS: бэкпорт фикса в changelog
rpm -q --changelog kernel | grep -E "CVE-2026-46242|a6dc643c6931"
# Debian/Ubuntu: аналогичная проверка
apt changelog linux-image-$(uname -r) 2>/dev/null | grep -E "CVE-2026-46242|eventpoll"
a6dc643c6931, ced39b6a8062, ef4ca02e9536.Приоритет патчинга:
- Multi-tenant серверы и Kubernetes worker nodes - атакующий из одного пода может получить root на ноде, после чего - lateral movement по кластеру
- CI/CD runners - compromised dependency -> LPE -> секреты сборки и артефакты
- Developer workstations - stolen token или malicious package -> полная компрометация рабочей станции
- Android устройства на ядре 6.6+ (Pixel 10 и новее) - ожидайте OTA-обновление от Google
Детекция Bad Epoll и её ограничения
Файловых IOC нет - Bad Epoll это exploitation primitive, а не malware-семейство. Детекция вынужденно контекстная.KASAN не ловит Bad Epoll после фикса CVE-2026-43074. Исследователь указывает это как одну из причин, почему AI-модель пропустила баг. KFENCE (sampling-based детектор) теоретически может обнаружить UAF при удачном тайминге, но гарантий нет.
Audit syscalls (
epoll_create, epoll_ctl, close) через auditd возможен, но шум колоссальный - epoll используется каждым сетевым сервисом. Паттерн "быстрое создание и уничтожение множества epoll-дескрипторов в tight loop" может быть поведенческим индикатором, но требует калибровки порога под конкретную нагрузку. На production-сервере с nginx под нагрузкой отличить эксплойт от легитимного трафика - та ещё задача.Правило для incident response: если непривилегированный код исполнялся на хосте с уязвимым ядром в период до патчинга - предполагайте kernel-level компрометацию, даже если application logs показывают только userspace-событие. Стандартный набор: изоляция хоста, сбор volatile evidence, проверка на persistence - kernel modules, изменённые бинарники, crontab, SSH-ключи.
Bad Epoll vs Copy Fail: Linux kernel privilege escalation 2026
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Два месяца от первого репорта до корректного патча для бага в 2500 строках кода - факт, который стоит запомнить. Мейнтейнеры ядра - одна из сильнейших команд в open source, и первый фикс оказался неполным. Race condition в core kernel subsystem - класс ошибок, где один code review или автоматический тест не гарантирует ничего. Frontier AI-модель Mythos нашла одну гонку в том же участке кода и пропустила соседнюю - это объективный индикатор сложности, а не анекдот.
Большинство команд ставят приоритет kernel LPE ниже, чем remote RCE. По CVSS формально логично: AV:L < AV:N. Но в реальных инцидентах 2026 года именно local exploitation оказывается звеном, которое конвертирует "один скомпрометированный CI runner с low-priv shell" в "root на Kubernetes ноде с доступом ко всему кластеру". Bad Epoll, Copy Fail, их предшественники - это паттерн: каждый год минимум 2-3 публичных kernel LPE с рабочим PoC, и каждый раз - недели на раскатку патчей по продакшн-серверам.
С ростом AI-assisted vulnerability research количество обнаруженных kernel race condition будет расти. Mythos нашла CVE-2026-43074, следующая итерация модели найдёт три подобных бага за тот же цикл. Окно между disclosure и patch availability сократится. А вот окно между patch availability и фактическим обновлением - нет. Кто по-прежнему считает «только локальный» аргументом для отложенного патчинга - рано или поздно обнаружит, что "локальный" давно стал "любой поток с shell-доступом в вашем кластере". Проверьте
uname -r на своих нодах прямо сейчас. Если версия попадает в таблицу выше - патч нужен был вчера.
Последнее редактирование модератором: