
Эксплойт proof-of-concept для локального повышения привилегий в Linux, эксплуатирующий состояние гонки в таблице скоростей qdisc и использующий групминг кучи BPF и утечку через pipe для получения прав root.

Этот репозиторий содержит proof-of-concept эксплойт для локального повышения привилегий через CVE-2026-68138 — гонку в коде rate-table подсистемы управления трафиком Linux. В протестированном окружении QEMU PoC повышает привилегии от обычного процесса с внешним UID 1000 до шелла с UID 0 в исходном пользовательском пространстве имён.
Предупреждение
Этот код намеренно повреждает состояние кучи ядра. Используйте его только в изолированной одноразовой ВМ, которая принадлежит вам. Пропущенная гонка или преждевременная очистка могут вызвать панику гостевой системы. Не запускайте его на рабочей станции, сервере или в чужой системе.
qdisc_get_rtab() и qdisc_put_rtab() управляют глобальным для процесса односвязным списком qdisc_rtab_list и обычным неатомарным int refcnt. Исторически вызывающий код удерживал мьютекс RTNL, который сериализовал доступ к списку и счётчику ссылок.
Классификатор flower устанавливает TCF_PROTO_OPS_DOIT_UNLOCKED. В результате запрос RTM_NEWTFILTER для правила flower может достичь police-действия и вызвать вспомогательные функции rate-table qdisc без RTNL:
tc_new_tfilter()
-> fl_change()
-> tcf_exts_validate_ex()
-> tcf_action_init()
-> tcf_police_init()
-> qdisc_get_rtab()/qdisc_put_rtab()
Одновременные запросы, использующие одну и ту же rate-table, могут конкурировать за глобальный список и его счётчик ссылок. Результат — use-after-free или double-free объекта struct qdisc_rate_table, 1056-байтовой структуры, выделяемой из kmalloc-2k на протестированном ядре x86-64.
Поскольку список глобальный, а не на каждое сетевое пространство имён, запросы из разных сетевых пространств имён всё равно конкурируют за один и тот же объект.
Запись Linux CNA определяет коммит, вносящий уязвимость: 470502de5bdb, выпущенный в Linux 5.1.
Дистрибутивные ядра часто бэкпортируют исправления, не меняя версию на указанную выше upstream. Проверьте, присутствует ли в точном исходном коде ядра, используемого системой, один из исправляющих коммитов — или эквивалентное изменение со spinlock в rate-table qdisc.
Эксплойт разработан и проверен против уязвимого коммита 92d3817649df2b0b6a008a686c8275c88d7ef594, непосредственного родителя исправления в mainline. В качестве контроля на исправленном ядре использовался f43ee0c0730d6191629b5ee1ceae27b1ebfdc047.
По состоянию на 2026-08-12 поиск по CVE-трекеру Ubuntu не вернул запись об этой CVE. Поэтому таблица ниже — результат непосредственного анализа исходного кода, а не определение статуса безопасности от Canonical. Каждый связанный тег Ubuntu по-прежнему содержит незаблокированный qdisc_rtab_list и не содержит исправляющий qdisc_rtab_lock.
На дату проверки исправленных пакетов Ubuntu не выявлено. Будущие пакеты Ubuntu следует проверять на наличие бэкпорта, эквивалентного указанным исправлениям upstream, а не оценивать только по номеру версии.
Специфичный для сборки Ubuntu 22.04 эксплойт, лаборатория QEMU, точная контрольная сумма образа и условия описаны в ubuntu/README.md. Он проверен на официальном ядре 5.15.0-187-generic #197-Ubuntu с включённым учётом памяти cgroup.
Лежащая в основе ошибка и эта конкретная цепочка эксплойта имеют разные требования. PoC проверен при следующих условиях:
CONFIG_USER_NS=y и CONFIG_NET_NS=y;CONFIG_NET_CLS=y, CONFIG_NET_CLS_FLOWER=y,
CONFIG_NET_CLS_ACT=y и CONFIG_NET_ACT_POLICE=y;CONFIG_TMPFS_XATTR=y для heap-spray через simple_xattr;CONFIG_MODULES=y и рабочий /sbin/modprobe для финального root-хелпера;CONFIG_MEMCG=n, чтобы размещения qdisc/BPF/pipe/xattr, используемые этой
цепочкой, попадали в ожидаемый кэш kmalloc-2k;CONFIG_SLAB_BUCKETS=y, рандомизация freelist и усиление freelist были включены в успешно протестированном ядре. KASLR не обходится сам по себе с помощью жёстко заданного адреса: PoC получает необходимые указатели на страницу и операции из pipe-утечки. Поставляемая лаборатория использовала nokaslr для упрощения отладки.
Ядра с включённым учётом памяти cgroup или иной компоновкой аллокатора/кэшей требуют другой стратегии переиспользования памяти. PoC намеренно не претендует на переносимость между произвольными конфигурациями дистрибутивов.
Отдельный вариант для Ubuntu реализует эту другую стратегию переиспользования; его требования намеренно уже и перечислены в ubuntu/README.md.
Четыре рабочих потока входят в отдельные сетевые пространства имён и создают flower-фильтры с police-действиями. Три потока идут по пути успешного действия; один передаёт намеренно недопустимый estimator после получения обеих ссылок на rate-table, заставляя выполниться путь очистки. Такая комбинация делает воспроизводимыми одновременные операции со счётчиком ссылок и списком без совместного использования состояния flower-классификатора между потоками.
qdisc_rate_table с помощью классического BPFПосле каждого netlink-запроса тот же самый CPU немедленно прикрепляет классический BPF-фильтр из 133 инструкций. Его массив инструкций размером 1064 байта выделяется из kmalloc-2k и сформирован так, что байты, перекрывающие qdisc_rate_table.next и qdisc_rate_table.refcnt, изначально остаются корректными.
Последняя инструкция содержит маркер для каждого сокета. SO_GET_FILTER позволяет PoC обнаружить, когда указатель orig_prog->filter одного сокета был перенаправлен на другое живое BPF-размещение. В результате получаются два объекта сокета, ссылающихся на один и тот же буфер инструкций.
Закрытие одного владельца откладывает фактическое освобождение через sk_filter_release_rcu(). После grace-периода PoC выделяет pipe-кольца на 32 слота:
32 * sizeof(struct pipe_buffer) = 32 * 40 = 1280 bytes -> kmalloc-2k
Одно из колец переиспользует алиасированный BPF-буфер. Чтение другого сокета через SO_GET_FILTER раскрывает живой pipe_buffer, включая указатели page и ops, а также смещение записи /sbin/modprobe в page cache.
Закрытие второго сокета освобождает живое pipe-кольцо после очередного grace-периода RCU, в то время как соответствующий pipe_inode_info всё ещё ссылается на него. Spray через simple_xattr переиспользует кольцо размером 1280 байт и помещает утёкший pipe_buffer в ожидаемый слот с установленным флагом PIPE_BUF_FLAG_CAN_MERGE.
Запись в каждый кандидатный pipe в конечном счёте добавляет управляемые атакующим байты на страницу page cache, поддерживающую точку входа ELF файла /sbin/modprobe. PoC считывает файл обратно и продолжает работу только после подтверждения точной перезаписи.
Современные ядра больше не используют прежний запасной вариант автозагрузки модулей через недопустимый binfmt в search_binary_handler(). Вместо этого PoC создаёт дейтаграммный сокет AF_INET с неподдерживаемым протоколом 253. inet_create() запрашивает отсутствующий модуль net-pf-2-proto-253-type-2, что заставляет ядро выполнить перезаписанный /sbin/modprobe с правами root в исходном пространстве имён.
Внедрённая в точку входа нагрузка записывает proof-файл и открывает root-шелл на последовательной консоли QEMU.
Бинарный файл гостевой системы скомпилирован статически:
./build.sh
Эквивалентная команда:
gcc -O2 -static -pthread -Wall -Wextra -Werror \
-o build/exploit exploit.c
Полученный бинарный файл должен быть доступен внутри гостевой системы как /exploit/CVE-2026-68138. Протестированная лаборатория использовала общую папку QEMU 9p с доступом на чтение и запись. Root-хелпер ожидает эту общую папку по пути /chroot/exploit из исходного пространства имён монтирования; измените helper_command в exploit.c, если лаборатория использует другую компоновку.
Соответствующие параметры тестового ядра записаны в config.fragment. Это фрагмент, а не полная конфигурация производственного ядра.
Из непривилегированного шелла гостевой системы:
id
/exploit/CVE-2026-68138
Гонка носит вероятностный характер. При неудаче PoC выводит:
[-] no BPF alias found in this attempt
Перезагрузите ВМ перед повторной попыткой. Не запускайте его повторно в той же гостевой системе: глобальный список qdisc может быть уже повреждён.
При успешном выполнении выводится:
[+] confirmed dangling orig_prog->filter pointer
[+] pipe leak: page=<kernel pointer> ops=<kernel pointer> off=0x60f len=1 flags=0
[+] page-cache overwrite via dangling pipe
[+] controlled page-cache write confirmed; triggering helper
[+] CVE-2026-68138 initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
[+] ROOT PROOF: uid=0(root) gid=0(root) groups=0(root)
Полная очищенная запись вывода находится в docs/example-output.txt.
Полная цепочка была выполнена три раза при свежих загрузках уязвимой ВМ. Финальный бинарный файл показал:
outer identity: uid=1000(user)
alias: classic-BPF orig_prog->filter overlap
leak: live pipe_buffer page and ops pointers
write: verified /sbin/modprobe page-cache modification
final identity: uid=0(root) gid=0(root) groups=0(root)
Тот же бинарный файл был запущен против исправленного коммита с той же конфигурацией из четырёх vCPU. Было выполнено примерно 126 000 гоночных запросов без появления BPF-алиаса, и ни один из этапов с pipe или page cache не был достигнут.
Специфичный для Ubuntu вариант также был запущен против точно соответствующего официального generic-ядра 5.15.0-187.197. Гостевая система загружалась с четырьмя vCPU, обычным учётом памяти cgroup и nokaslr в качестве единственного специфичного для эксплойта аргумента ядра.
Ядро Ubuntu не перекомпилировалось: тест использовал неизменённые vmlinuz-5.15.0-187-generic из дистрибутива и соответствующие официальные модули. Таблица предварительных требований Ubuntu отделяет штатные возможности ядра от изменений в окружении выполнения и лаборатории QEMU.
Исходная учётная запись не имела доступа sudo и дополнительных групп:
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
Из свежего снапшота эксплойт получил BPF-алиас, переиспользовал его через объекты simple_xattr в cgroup-v2 и блокирующие битмапы select(), проверил, что /proc/sys/kernel/modprobe содержит /tmp/x, и достиг:
[+] CVE-2026-68138 Ubuntu initial-namespace root shell
uid=0(root) gid=0(root) groups=0(root)
root@cve-2026-68138-jammy:/# id
uid=0(root) gid=0(root) groups=0(root)
Смотрите реальную запись QEMU и инструкции по лаборатории Ubuntu. Вариант жёстко прописывает адрес modprobe_path для этого пакета и поэтому требует nokaslr; он не заявляет об обходе KASLR или совместимости с другой сборкой Ubuntu.
Патч уже является частью ядра Linux и поэтому не продублирован в этом репозитории. Канонические исправляющие коммиты для stable и mainline указаны в ссылках ниже.
Исследование и реализация эксплойта: A. Ramos <[email protected]> (Twitter: @aramosf).
| Ветка | Статус | Коммит/версия |
|---|
| Linux до 5.1 | Не затронута | Вносящий изменение коммит отсутствует |
| Linux с 5.1 по 7.1.5 | Затронута, если нет вендорного бэкпорта | 470502de5bdb до коммита перед стабильным исправлением |
| Linux 7.1.y | Исправлена | 7.1.6, fb29e1b41052 |
| Серия разработки Linux 7.2 | Затронута до rc5 | rc1 – rc4 |
| Mainline | Исправлена | 7.2-rc5, f43ee0c0730d |
| Линейка Ubuntu | Проверенный пакет/тег | Результат анализа исходного кода |
|---|
| Ubuntu 22.04 GA | 5.15.0-187.197 | Уязвимый код присутствует; полный эксплойт воспроизведён в QEMU |
| Ubuntu 22.04 HWE | 6.8.0-136.136~22.04.1 | Уязвимый код присутствует; цепочка эксплойта не тестировалась |
| Ubuntu 24.04 HWE | 7.0.0-28.28~24.04.1 | Уязвимый код присутствует; эта цепочка эксплойта несовместима с его усилением аллокатора |
| Ubuntu 26.04 | 7.0.0-28.28 | Уязвимый код присутствует; эта цепочка эксплойта несовместима с его усилением аллокатора |