
Эксплойт PoC на Rust для LPE CVE-2026-31431 в ядре Linux (запись в page-cache через AF_ALG), а также защита на этапе выполнения с помощью eBPF, блокирующая создание сокетов AF_ALG через LSM или kprobe без перезагрузки.
CVE-2026-31431 (Copy Fail) — это локальная уязвимость повышения привилегий в модуле algif_aead ядра Linux (подсистема AF_ALG). Логическая ошибка, внесённая в августе 2017 года, позволяет любому непривилегированному локальному пользователю записать 4 контролируемых байта в page cache любого читаемого файла через AF_ALG + splice(), а затем выполнить повреждённый setuid-бинарник для получения root. Эксплойт детерминированный — без гонок, без офсетов ядра, без краха системы. Он затрагивает все основные дистрибутивы Linux с ядрами от 4.14 до 7.0-rc.
CVSS: 7.8 | Исправлено в: ядро 7.0, 6.19.12, 6.18.22 | Исправление в mainline: коммит a664bf3d603d
Источники: copy.fail, The Hacker News, уведомление CloudLinux
crates/
├── exp/ # Эксплойт PoC (реализация на Rust)
├── copy_fail_guard/ # Пользовательский загрузчик eBPF — режим LSM
├── copy_fail_guard-ebpf/ # Программа eBPF LSM (не в workspace)
├── copy_fail_guard_kprobe/ # Пользовательский загрузчик eBPF — режим kprobe
└── copy_fail_guard_kprobe-ebpf/ # Программа eBPF kprobe (не в workspace)
scripts/
└── build_guard.sh # Сборка в один клик → вывод в dist/
Корневая причина — цепочка из трёх независимых функций ядра, небезопасно взаимодействующих друг с другом:
AF_ALG — открывает крипто-API ядра непривилегированному пользовательскому пространствуsplice() — передаёт данные файла с нулевым копированием как ссылки на page cache (не копии) в scatterlist криптоподсистемыauthencesn — использует выходной буфер вызывающего как временное пространство, записывая 4 байта по адресу dst[assoclen + cryptlen]В 2017 году оптимизация на месте в algif_aead.c (72548b093ee3) сделала req->src == req->dst, связав страницы page cache с доступным для записи выходным scatterlist. Когда authencesn записывает свои временные байты, он выходит за пределы выходного буфера в связанные страницы page cache. Атакующий контролирует:
Повреждённая страница никогда не помечается как грязная — файл на диске не затрагивается, но execve() читает из page cache. Повредите setuid-бинарник → root.
Реализация на Rust публичного PoC на Python объёмом 732 байта. Нацелен на /usr/bin/su, вставляет его страницы page cache в AEAD-сокет AF_ALG и перезаписывает их сжатой shell-нагрузкой.
Требуется Rust 1.85+ (edition 2024).
cargo build --release -p copy_fail
Бинарник только для Linux. На других платформах он завершается с ошибкой Unsupported.
Предупреждение: Это эксплуатирует реальную уязвимость ядра. Запускайте только на системах, которыми вы владеете и управляете, в идеале — на одноразовой виртуальной машине.
Загрузите ВМ с уязвимым ядром (любой массовый дистрибутив с ядром < 7.0 / < 6.19.12 / < 6.18.22).
Скопируйте собранный бинарник в ВМ и запустите его от непривилегированного пользователя:
./target/release/copy_fail
Уязвимая система: появляется root-шелл (#). Выполните whoami, чтобы подтвердить root.
Не уязвимая система (исправленное ядро): операция AF_ALG завершается ошибкой, или бинарник su ведёт себя нормально. Вы увидите ошибку или обычный запрос пароля su.
Средство защиты ядра в реальном времени, которое блокирует CVE-2026-31431 без обновления ядра или перезагрузки. Оно использует eBPF для перехвата создания сокетов AF_ALG, отсекая первый шаг эксплойта.
Предоставляются два режима. Загрузчик в один клик автоматически выбирает лучший доступный режим:
| Режим LSM | Режим kprobe | |
|---|---|---|
| Как блокирует | Возвращает -EPERM (создание сокета запрещено) | SIGKILL (процесс убит) |
| Требования к ядру | ≥ 5.7 с параметром загрузки lsm=bpf | ≥ 5.3, без специальных параметров |
| Нужна перезагрузка для включения | Возможно (если lsm=bpf ещё не задан) | Нет |
| Точка перехвата | LSM-хук socket_create | kprobe на __sys_socket |
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • Определяет поддержку BPF LSM │
│ • LSM доступен → copy_fail_guard (EPERM) │
│ • LSM недоступен → copy_fail_guard_kprobe (KILL)│
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ Программа eBPF │
│ если семейство сокета == 38 (AF_ALG) │
│ → блокировать (EPERM или SIGKILL) │
│ иначе │
│ → разрешить │
└──────────────────────────────────────────────────┘
Блокировка AF_ALG оказывает почти нулевое влияние на типичные системы:
AF_ALGAF_ALG (например, OpenSSL с включённым движком afalg, некоторые пути аппаратного ускорения криптографии во встраиваемых системах)socket(AF_ALG, ...)Сборка в один клик (рекомендуется):
./scripts/build_guard.sh
Это автоматически устанавливает недостающие тулчейны (nightly, bpf-linker), компилирует все программы eBPF и пользовательские загрузчики и выводит всё в dist/:
dist/
├── copy_fail_guard.bpf.o # Программа eBPF LSM
├── copy_fail_guard_kprobe.bpf.o # Программа eBPF kprobe
├── copy_fail_guard # Пользовательский загрузчик (LSM)
├── copy_fail_guard_kprobe # Пользовательский загрузчик (kprobe)
└── run_guard.sh # Автовыбор: sudo ./run_guard.sh
Скопируйте каталог dist/ на любую целевую машину и выполните sudo ./run_guard.sh для активации защиты.
Ручная сборка (по шагам):
Шаг 1: Скомпилируйте программы eBPF (обязательно на Linux):
# Вариант LSM
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# Вариант kprobe
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
Шаг 2: Соберите пользовательские загрузчики:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
Рекомендуется — автоматический выбор лучшего режима:
sudo ./dist/run_guard.sh
Вручную — запуск конкретного режима:
# Режим LSM (требуется lsm=bpf)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# Режим kprobe (работает везде)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Нажмите Ctrl-C, чтобы отключить программу eBPF и восстановить нормальное поведение.
С запущенной защитой в одном терминале:
# В другом терминале попробуйте эксплойт:
./target/release/copy_fail
# Режим LSM: "error: Operation not permitted"
# Режим kprobe: "已杀死" / "Killed"
# Или проверьте напрямую через Python:
python3 -c "import socket; socket.socket(38, 5, 0)"
# Режим LSM: PermissionError: [Errno 1] Operation not permitted
# Режим kprobe: Killed