Блог

pedit COW (CVE-2026-46331): ядро Linux переписывает кэш чужого файла — и локальный пользователь становится root

LinuxKernel_CVE-2026-46331_pedit_COW
CVE / Linux / Безопасность

pedit COW (CVE-2026-46331): ядро Linux переписывает кэш чужого файла — и локальный пользователь становится root

В конце мая 2026 года на почтовом листе netdev появился патч с темой «net/sched: fix pedit partial COW leading to page cache corruption». Ничего подозрительного — рутинное исправление повреждения данных в подсистеме управления трафиком. Никакого CVE, никакой пометки security. Патч обсуждался публично несколько недель. Потом, 16 июня, при мёрже в mainline ядра, ядерный CNA автоматически присвоил идентификатор — CVE-2026-46331. На следующий день, 17 июня, на GitHub появился рабочий эксплойт. Команды безопасности, которые не следят за netdev mailing list, узнали об этом только когда получили сообщения от клиентов.

CVE-2026-46331, получивший название pedit COW — это уязвимость в подсистеме управления трафиком ядра Linux (tc), конкретно в функции tcf_pedit_act(). Технически это out-of-bounds write (CWE-787), который позволяет непривилегированному локальному пользователю записать данные в page cache произвольного файла, доступного для чтения. Записать туда, куда нельзя. Включая кэшированный образ setuid-бинарника — например /usr/bin/su. NVD на момент публикации не финализировал CVSS-оценку, Red Hat оценивает уязвимость как Important с вектором локального доступа, низкой сложностью и без необходимости взаимодействия пользователя. PoC опубликован и работает на RHEL 10, Debian 13 и Ubuntu 24.04. Red Hat выпустил errata для RHEL 8, 9 и 10. Debian закрыл trixie. Ubuntu пока не выпустил патч для поддерживаемых релизов.

ЧТО ТАКОЕ TC И ACT_PEDIT

Утилита tc (traffic control) — это инструмент управления трафиком в ядре Linux. С её помощью можно управлять очередями пакетов, ограничивать полосу пропускания, расставлять приоритеты и — что важно для этой уязвимости — переписывать заголовки пакетов прямо в ядре, не выходя в userspace. Это нужно, например, для перемаркировки пакетов по DSCP, подмены VLAN-тегов или изменения IP-адресов без conntrack.

Для переписывания заголовков существует действие act_pedit (packet edit). Оно принимает список «ключей» — каждый ключ описывает смещение в пакете и новое значение, которое нужно туда записать. Смещения бывают двух типов: статические (известны при конфигурации) и типизированные (resolved at runtime — конкретное значение зависит от типа пакета, например от того, есть ли у него IP-заголовок). Именно это разделение и стало причиной уязвимости.

Важный момент: act_pedit не требует привилегий root. Непривилегированный пользователь может создать user namespace, получить внутри него сетевые привилегии (CAP_NET_ADMIN) и загрузить модуль act_pedit. Именно эта комбинация — user namespaces + act_pedit — формирует вектор атаки.

КАК РАБОТАЕТ БАГ

Когда tcf_pedit_act() обрабатывает пакет, ей нужно убедиться что область памяти, куда она пишет, является приватной копией — то есть не разделяется с другими процессами. Представьте библиотечную книгу: читать её могут все, но если вы хотите сделать пометки — библиотекарь сначала делает для вас ксерокопию, чтобы вы не испортили оригинал. Это и есть copy-on-write (COW): перед записью ядро делает приватную копию нужных страниц памяти. Для этого вызывается skb_ensure_writable(), которой передаётся диапазон байт, который предполагается изменить.

Проблема в том, что этот диапазон вычисляется один раз до начала цикла по ключам, используя поле tcfp_off_max_hint. Это поле содержит максимальное смещение из статических ключей — тех, чьи смещения известны заранее. Но для типизированных ключей реальное смещение вычисляется позже, во время обработки каждого конкретного пакета. Если это runtime-смещение выходит за пределы диапазона, переданного в skb_ensure_writable(), то соответствующая страница памяти не была скопирована приватно. Запись уходит в shared page — то есть прямо в page cache.

Патч исправляет это перемещением вызова skb_ensure_writable() внутрь цикла по ключам, чтобы диапазон вычислялся по реальному смещению каждого ключа. Дополнительно добавлена проверка переполнения в арифметике смещений и обработка отрицательных смещений (например, при редактировании Ethernet-заголовков) через skb_cow().

КАК ЭТО ЭКСПЛУАТИРУЕТСЯ

Атака строится на том, что page cache является глобальным — страницы файла в кэше разделяются между всеми процессами, которые его читают. Если атакующий может записать данные в кэшированную страницу файла, следующий execve() этого файла выполнит уже отравленный образ из кэша, а не оригинал с диска. Проверки целостности файла при этом ни о чём не сообщат — на диске файл не изменился.

Конкретная механика: атакующий открывает сокет на loopback-интерфейсе, создаёт TCP-соединение на 127.0.0.1:4445, настраивает tc-правило с act_pedit, использующим типизированный ключ с runtime-смещением, выходящим за пределы COW-диапазона. Данные передаются через sendfile() — и это принципиальный выбор: в отличие от обычного read() + write(), sendfile копирует страницы файла из page cache напрямую в сокет, минуя userspace. Именно это даёт ядру «увидеть» реальные страницы файла в момент прохода через act_pedit. Когда пакет проходит через act_pedit, запись уходит в page cache файла-источника, а не в приватную копию. Для эксплуатации достаточно O_RDONLY дескриптора — файл не нужно открывать на запись.

Цель выбирается с учётом setuid-бита: /usr/bin/su, /usr/bin/passwd или любой другой setuid-бинарник, доступный для чтения. После записи полезной нагрузки в кэш атакующий запускает бинарник — и получает shell с правами root, пока кэш содержит отравлённый образ. Сброс page cache (echo 3 > /proc/sys/vm/drop_caches) очищает отравленный образ, но не закрывает уже открытый root shell.

ЧТО ПРОИСХОДИТ ДАЛЬШЕ — ЗАВИСИТ ОТ КОНФИГУРАЦИИ

Два условия определяют эксплуатируемость системы: загружаемость act_pedit и открытость unprivileged user namespaces. На RHEL 10 и Debian 13 оба условия выполняются по умолчанию — эксплойт работает без дополнительных флагов. На Ubuntu 24.04 AppArmor ограничивает создание user namespaces, но исследователи подтвердили обход через существующие AppArmor-профили, допускающие user namespaces, — атака работает, просто требует чуть больше подготовки. Ubuntu 26.04 блокирует этот путь настройками AppArmor по умолчанию, но само ядро под ними остаётся уязвимым.

На системах с disabled unprivileged user namespaces (user.max_user_namespaces=0 на RHEL, kernel.unprivileged_userns_clone=0 на Debian/Ubuntu) эксплойт в публичном виде не работает — атакующему не из чего получить CAP_NET_ADMIN. Это и есть главный workaround для тех, кто не может немедленно поставить патч. Цена: сломаются rootless-контейнеры, некоторые CI-сандбоксы и браузеры с изолированными процессами. На продакшн-сервере без Docker в rootless-режиме это обычно приемлемая жертва.

ХРОНОЛОГИЯ

Конец мая 2026 года — патч появился на netdev mailing list как исправление повреждения данных, без CVE и без security framing. Технические детали эксплуатируемого бага лежали в публичном архиве несколько недель. 13 мая CVE-2026-46331 был зарезервирован ядерным CNA. 16 июня — мёрж в mainline, CVE опубликован. 17 июня — рабочий PoC на GitHub. Red Hat выпустил первые errata (RHSA-2026:27288 для RHEL 10) в течение нескольких дней после публикации CVE. Debian выпустил DSA-6355-1 для trixie. AlmaLinux получил ALSA-2026:27353 для версии 8.

TuxCare в своём отчёте прямо написал: «We did not catch this one through our own monitoring. It came in through a customer escalation — it was never on oss-security, and the kernel CNA stream is not currently on our watchlist». Это не признание конкретной компании — это системная проблема мониторинга kernel security. Патчи в upstream ядра нередко идут без явной security маркировки, а CVE назначается только при мёрже. Разрыв между «патч публично доступен» и «команды безопасности узнали» может составлять недели.

ПОЧЕМУ ЭТО ВАЖНО

pedit COW продолжает серию page cache corruption LPE, которая в 2026 году стала заметным трендом в ядре Linux: Dirty Frag, Copy Fail, теперь pedit COW. Все три эксплуатируют один и тот же принцип — ядро пишет в разделяемую страницу памяти вместо приватной копии, отравляя кэш исполняемого файла. Механизм COW существует в ядре с начала — и каждый раз оказывается, что где-то диапазон вычислен не там или не так.

Red Hat оценивает уязвимость как Important — с вектором локального доступа, без требований к привилегиям и без необходимости взаимодействия пользователя. Это означает: злоумышленник, получивший shell на вашем сервере — через уязвимый веб-сервис, через скомпрометированный контейнер, через любой другой вектор — может использовать CVE-2026-46331 как ступеньку к root. Особенно актуально для CI/CD-раннеров, build workers, shared research-машин и любых систем, где локальный доступ имеют несколько пользователей или автоматизированные процессы.

Отдельный риск — file integrity monitoring пропустит атаку. Потому что файл на диске не меняется. AIDE, Tripwire, любой FIM, работающий с хешами файлов на диске, не увидит ничего подозрительного. Отравленный образ существует только в памяти — пока кэш не сброшен или система не перезагружена. Это делает атаку особенно аккуратной: следов на диске нет, доступ к root есть. Для расследования инцидента это означает, что forensics-анализ файловой системы ни о чём не скажет — момент компрометации можно будет восстановить только по сетевым логам, системным вызовам через auditd или метаданным запущенных процессов.

ОБНОВЛЕНИЕ

Самый надёжный путь — установить патч и перезагрузиться. Перезагрузка важна: она одновременно загружает исправленное ядро и сбрасывает page cache, очищая любые потенциально отравленные страницы. Прежде чем применять workaround с блокировкой модуля, проверьте используется ли act_pedit в вашей конфигурации — если вывод пустой, модуль активно не задействован:

tc actions list action pedit

Проверьте текущую версию ядра командой uname -r и сравните с исправленными версиями для вашего дистрибутива.

На RHEL 8 патч входит в RHSA-2026:27353, на RHEL 8.8 EUS/TUS — в RHSA-2026:27355, на RHEL 9 — в RHSA-2026:27789, на RHEL 10 — в RHSA-2026:27288. AlmaLinux 8 получил ALSA-2026:27353. Конкретные версии пакетов ядра указаны на страницах errata на access.redhat.com. Для Debian 13 (trixie) — DSA-6355-1, уязвимая версия 6.12.90+deb13.1, исправленная — 6.12.94-1 и выше. Ubuntu на момент публикации патч ещё не выпустила для поддерживаемых релизов — следите за Ubuntu Security Notices (USN). PoC автор проверял на RHEL 10.0 с ядром 6.12.0-228.el10 и Debian 13 с ядром 6.12.90+deb13.1 — обе версии уязвимы.

После обновления убедитесь что загружено новое ядро. На RHEL/AlmaLinux список установленных версий ядра и текущую активную покажет команда ниже — активная помечена звёздочкой:

rpm -q kernel

На Debian/Ubuntu проверьте версию загруженного ядра:

uname -r
dpkg -l linux-image-* | grep ^ii
# RHEL / AlmaLinux
dnf update kernel && systemctl reboot

# Debian 13
apt update && apt install linux-image-$(uname -r) && reboot

Если установить патч немедленно невозможно — два workaround убивают цепочку эксплуатации. Первый: заблокировать загрузку модуля act_pedit, если у вас нет tc pedit-правил. Проверьте что модуль не нужен командой lsmod | grep act_pedit, и если вывод пустой — заблокируйте:

echo 'install act_pedit /bin/true' | sudo tee /etc/modprobe.d/disable-act_pedit.conf

Второй: отключить unprivileged user namespaces. На Debian и Ubuntu это два параметра — оба нужны:

# Debian / Ubuntu
echo 'kernel.unprivileged_userns_clone=0' | sudo tee /etc/sysctl.d/99-userns.conf
echo 'user.max_user_namespaces=0' | sudo tee -a /etc/sysctl.d/99-userns.conf
sudo sysctl -p /etc/sysctl.d/99-userns.conf

На RHEL-семействе достаточно одного:

# RHEL / AlmaLinux
echo 'user.max_user_namespaces=0' | sudo tee /etc/sysctl.d/99-userns.conf
sudo sysctl -p /etc/sysctl.d/99-userns.conf

Перед применением второго workaround проверьте что rootless Docker, Podman и аналоги не используются — они зависят от unprivileged user namespaces. На продакшн-сервере без rootless-контейнеров ограничение обычно безопасно.

ВЫВОДЫ

pedit COW — третья page cache LPE в ядре Linux за 2026 год, и механика везде одна: неправильно вычисленный COW-диапазон, запись в shared страницу, отравление кэша. Dirty Frag жила в IPsec. Copy Fail — в crypto. Теперь act_pedit в tc. Одна и та же ошибка в разных местах — что говорит о том, что проблема системная, а не локальная.

CVE-2026-46331 пришла из публичного патча на netdev без какого-либо security-сигнала. PoC появился через день после CVE. Между «патч доступен для чтения любому» и «большинство команд узнали о проблеме» прошло несколько недель. Если ваш мониторинг ядерной безопасности ограничивается подпиской на oss-security и vendor advisories — вы узнаёте об этих вещах с задержкой. Netdev mailing list, kernel git tags, upstream stable notes — всё это тоже источники, которые стоит мониторить.

Обновите ядро, перезагрузитесь. Если патч ещё не вышел для вашего дистрибутива — заблокируйте act_pedit или отключите unprivileged userns до выхода обновления.

Leave your thought here

Ваш адрес email не будет опубликован. Обязательные поля помечены *