
Эксплойт для CVE-2026-46215, локальное повышение привилегий через use-after-free в DRM GEM ядра Linux. Использует гонки, распыление кучи и перезапись файлов в стиле Dirty Pipe, чтобы превратить непривилегированного пользователя render-node в root без пароля.
change_handle Use-After-Free (повышение привилегий без привилегий)Локальное повышение привилегий через use-after-free в ioctl ядра DRM
DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, номер ioctl 0xD2).
Доступен любому пользователю, имеющему доступ к render-узлу (/dev/dri/renderD*,
предоставляется активной сессии systemd-logind во всех основных дистрибутивах для
настольных ПК). Цепочка в этом репозитории приводит UAF к получению root без пароля
из-под непривилегированного пользователя.
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)53096728b891 ("drm: Add DRM prime interface to reassign GEM handle", David Francis / AMD), добавлен для работы AMD по CRIU5e28b7b94408). Данный ioctl также отключается в апстриме для 7.1 из-за этой и связанных гонок.Эта ошибка впервые была сообщена Путтиметом Таммасенгом (Puttimet Thammasaeng),
которому принадлежит апстрим-кредит Reported-by в исправлении. Я обнаружил и
сообщил о ней самостоятельно на [email protected] 2026-04-12. Моё сообщение было
подтверждено и передано мейнтейнерам, однако более раннее сообщение указано в
апстриме. Данный репозиторий содержит мой собственный анализ и эксплоит.
Writeup: https://cyberstan.co.uk
Исправление включено в выпущенные стабильные ядра (6.18.32 и 7.0.9 и новее). Этот репозиторий был опубликован после того, как исправления стали широко доступны.
drm_gem_change_handle_ioctl() перемещает GEM-объект от одного дескриптора к
другому, но никогда не корректирует obj->handle_count. Он также пропускает
drm_vma_node_allow/revoke и колбэки открытия/закрытия драйвера. Поскольку
handle_count остаётся равным 1, параллельный GEM_CLOSE на старом дескрипторе
уменьшает его до 0 и освобождает объект, в то время как новый дескриптор всё ещё
ссылается на него в IDR. Этот висящий дескриптор и есть use-after-free, который
затем разыменовывается в drm_gem_object_release_handle().
GEM_CHANGE_HANDLE против GEM_CLOSE для получения висящего дескриптора.pipe_buffer (msg_msg feng shui для подгонки kmalloc-512, затем трубы,
заполненные splice).pipe_buf_ops через информационный ioctl драйвера: obj->size (смещение 216)
перекрывается с pipe_buf[5].ops, что даёт указатель на ядро и базу KASLR.obj->name (смещение 224) попал на
pipe_buf[5].flags и установил PIPE_BUF_FLAG_CAN_MERGE (name = 16 = 0x10)./etc/passwd, root становится без пароля.Смещения проверены через pahole и специфичны для разметки в диапазоне 6.18–7.0.
Переопределите макросы GEM_* / PIPEBUF_* для других ядер.
poc.c — эксплоит. Собирайте статически с -lpthread.run_exploit.sh — собирает PoC и минимальную initramfs, загружает его в QEMU.qemu-system-x86_64, gcc, busybox (статический), fakeroot, cpio, gzip.
KVM (/dev/kvm) рекомендуется; гонка работает значительно надёжнее с ним.
Эксплоиту требуется уязвимая цель, собранная с определёнными опциями:
CONFIG_KASAN должен быть ВЫКЛЮЧЕН. KASAN изолирует освобождённые slab-объекты
и блокирует восстановление распылением труб, поэтому эксплоит не будет работать
против сборки с KASAN.virtio_gpu (используется в демо) или nouveau.CONFIG_DRM=y, CONFIG_DRM_VIRTIO_GPU=y, CONFIG_DEVTMPFS=y,
CONFIG_BLK_DEV_INITRD=y.Шаги:
CONFIG_KASAN не задан.make -j"$(nproc)" bzImage, что создаст arch/x86/boot/bzImage.Демо загружается с nokaslr, поэтому утёкший указатель детерминирован. Утечка
сама по себе обходит KASLR, так что режим с KASLR тоже работает, адрес просто
меняется при каждой загрузке.
Посмотрите на drivers/gpu/drm/drm_gem.c, функцию drm_gem_change_handle_ioctl().
Быстрая проверка:
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 означает УЯЗВИМО (нет обработки счётчика ссылок).По структуре:
file_priv->prime.lock, один
idr_alloc(&file_priv->object_idr, obj, ...), затем idr_remove() на старом
дескрипторе. Нет drm_gem_object_handle_get.idr_alloc, затем idr_replace(NULL) для
отсоединения старого дескриптора), при этом настоящий объект подменяется только
после успешного выполнения операций prime../run_exploit.sh /path/to/bzImage
Он собирает poc.c, помещает его в initramfs и загружает QEMU с
-device virtio-gpu-pci и nokaslr. PoC запускается как uid 1000 (без
привилегий), после чего скрипт выводит /etc/passwd до и после и даёт доступ к
оболочке. Выйдите из ВМ командой poweroff -f или Ctrl-A X.
[!] Race won (iter 977): handle=132049
[!] KASLR: pipe_buf_ops = 0xffffffff82428400
[!] EXPLOIT SUCCESSFUL
[!] FLINK: 16 = 0x10
[*] /etc/passwd:
root::0:0:pwned:/root:/bin/sh
[!] LPE CONFIRMED
[!] root account is now passwordless
Файл /etc/passwd, доступный только для чтения (chmod 444) и принадлежащий root,
перезаписывается непривилегированным процессом. Строка root: теряет поле пароля.
Пересоберите против исправленного дерева (6.18.32, 7.0.9, 7.1-rc3 или новее, либо любое дерево, проходящее проверку на исправление выше) и запустите снова:
./run_exploit.sh /path/to/patched-bzImage
На исправленном ядре эксплоит никогда не должен доходить до "EXPLOIT SUCCESSFUL": гонка больше не освобождает объект из-под нового дескриптора, поэтому утечка никогда не возвращает корректный указатель ядра.
Опубликовано в исследовательских и защитных целях после выхода исправлений в апстриме. Предоставляется как есть. Запускайте только на виртуальных машинах, которыми вы управляете.