
Глубокий технический анализ CVE-2026-31431 (Copy Fail) в ядре Linux — локальное повышение привилегий через ошибку scatterlist на месте в AF_ALG, включая первопричину, путь эксплуатации и руководство по отправке патча.
Серьёзность: CVSS 7.8 (высокая)
Класс: локальное повышение привилегий
Появилась: Linux 4.14 (2017)
Исправлена: коммит a664bf3d603d
CVE-2026-31431 — это уязвимость локального повышения привилегий в модуле algif_aead ядра Linux — интерфейсе AEAD-сокетов пользовательского крипто-API ядра (AF_ALG).
Уязвимость позволяет непривилегированному пользователю использовать адресное семейство AF_ALG в крипто-API ядра Linux для выполнения контролируемых 4-байтовых записей в page cache, который хранит копии файлов в памяти. Заменяя код в памяти копии читаемого исполняемого файла, хранящейся в page cache, злоумышленник может повысить привилегии пользователя, когда любой привилегированный процесс позже запустит эту изменённую версию файла.
В отличие от Dirty Cow (CVE-2016-5195), для которой требовалось выиграть гонку, Copy Fail — это прямолинейная логическая ошибка. Она срабатывает без гонок, повторов или нестабильных окон синхронизации.
Коммит 72548b093ee3 («crypto: algif_aead - switch to in-place processing») ввёл оптимизацию, которая устанавливала req->src == req->dst внутри aead_recvmsg(), устраняя необходимость выделения отдельного выходного буфера.
Эта оптимизация привела к тому, что req->src и req->dst стали указывать на объединённый scatterlist. Из-за этого страницы page cache из вызова splice() неправильно связывались напрямую в доступный для записи целевой scatterlist.
AEAD-API ядра определяет чёткий контракт вывода: буфер назначения получает AAD || plaintext, ровно assoclen + (cryptlen - authsize) байт. Ничто в API не обеспечивает соблюдение этого контракта, и нигде он не задокументирован как требование. Дизайн предполагает, что каждый AEAD-алгоритм ограничит свои записи предполагаемым назначением. Один алгоритм этого не сделал.
crypto/
├── algif_aead.c ← ошибка scatterlist на месте (основная)
└── authencesn.c ← запись за границы scratch-буфера (триггер)
crypto/algif_aead.c — присваивание на месте/* УЯЗВИМО — путь на месте после 2017 года */
static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
/* ... */
/*
* Когда пользовательское пространство вызывает splice(2) для файла,
* areq->tsgl содержит страницы page cache этого файла. Установка
* dst = src передаёт эти страницы page cache криптодвижку как
* доступный для записи выходной буфер.
*/
aead_request_set_crypt(req,
areq->tsgl, /* src */
areq->tsgl, /* dst — идентичный указатель; корневая причина */
areq->cryptlen, iv);
}
crypto/authencesn.c — запись за границы scratch-буфераШаблон authencesn — это AEAD-обёртка, используемая IPsec для поддержки расширенных порядковых номеров (ESN). IPsec использует 64-битные порядковые номера, разделённые на старшую половину (seqno_hi, байты 0–3 AAD) и младшую половину (seqno_lo, байты 4–7). Сетевой формат передаёт только seqno_lo; seqno_hi подразумевается.
/* crypto/authencesn.c — запись в scratch-буфер за границу вывода */
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
/* ... */
/*
* authencesn временно сохраняет seqno_hi по смещению
* (assoclen + cryptlen) в req->dst — на одно слово дальше
* легитимной области вывода.
*
* При нормальной работе: попадает в память, принадлежащую ядру.
* С scatterlist на месте: попадает в страницу page cache.
*/
scatterwalk_map_and_copy(&seqno_hi,
req->dst,
req->assoclen + req->cryptlen, /* смещение за границей */
sizeof(seqno_hi), /* 4 байта */
1 /* запись */);
/* authencesn не восстанавливает эти байты на пути ошибки */
}
Исправление в апстриме: коммит a664bf3d603d — откат оптимизации 2017 года.
crypto/algif_aead.c — раздельные scatterlist/* ИСПРАВЛЕНО — отдельные src и dst */
aead_request_set_crypt(req,
areq->tsgl, /* src — может содержать страницы page cache; только чтение */
areq->rsgl, /* dst — свежевыделенный буфер ядра; доступен для записи */
areq->cryptlen, iv);
Эффект: Страницы page cache остаются в src, который криптодвижок только читает. Запись в scratch-буфер authencesn по смещению assoclen + cryptlen теперь попадает в буфер, выделенный ядром и принадлежащий процессу. Ни одна страница page cache никогда не достижима из пути записи.
Ниже описано, как 4-байтовая запись обеспечивает полное повышение привилегий.
| Шаг | Действие |
|---|---|
| 1 | Открыть сокет AF_ALG, настроенный на authencesn(hmac(sha256),cbc(aes)) |
| 2 | Вызвать splice() для читаемого setuid-бинарника (например, /usr/bin/sudo) в сокет — это помещает его страницы page cache в areq->tsgl |
| 3 | Подобрать assoclen + cryptlen так, чтобы смещение записи в scratch-буфер совпало с целевым байтом внутри кэшированного образа бинарника |
| 4 | Вызвать recvmsg() — запускается путь расшифровки, authencesn записывает 4 контролируемых злоумышленником байта в page cache |
| 5 | Файл на диске не изменён; проверки целостности, inotify и временные метки inode не затронуты |
| 6 | Любой последующий запуск бинарника отображает уже изменённые страницы page cache — пропатченный код выполняется с euid=0 |
Запись попадает в кэшированные данные файла, переданного через splice(), в памяти, полностью обходя права доступа к файлу.
./scripts/get_maintainer.pl crypto/algif_aead.c
# Herbert Xu <[email protected]>
# [email protected]
./scripts/checkpatch.pl 0001-crypto-algif_aead-revert-inplace.patch
crypto: algif_aead - restore separate src/dst scatterlists in recvmsg
Commit 72548b093ee3 ("crypto: algif_aead - switch to in-place
processing") set req->src == req->dst inside aead_recvmsg(). When
the caller uses splice(2), the source scatterlist contains page
cache pages of the spliced file. With the in-place optimization,
those same pages become the writable destination passed to the
AEAD transform.
authencesn writes 4 bytes at offset (assoclen + cryptlen) into
req->dst as scratch space for Extended Sequence Number
rearrangement and does not restore them on the error path. With
page cache pages present in dst, this write bypasses the VFS
permission layer and corrupts the in-memory image of the spliced
file without modifying the on-disk inode.
An unprivileged local user can exploit this to overwrite
arbitrary bytes in the page cache of any readable setuid binary
and escalate privileges to root.
Restore separate src and dst scatterlists so that page cache
pages are confined to the read-only source path.
Fixes: 72548b093ee3 ("crypto: algif_aead - switch to in-place processing")
Cc: [email protected] # 4.14+
Reported-by: Taeyang Lee <[email protected]>
Signed-off-by: Your Name <[email protected]>
git send-emailgit send-email \
[email protected] \
[email protected] \
[email protected] \
0001-crypto-algif_aead-revert-inplace.patch
| Элемент | Назначение |
|---|---|
Тема подсистема: файл - глагольная фраза | Обязательный формат; не более 72 символов |
Fixes: <sha> ("оригинальный заголовок") | Запускает автоматический инструментарий стабильного бэкпорта |
Cc: [email protected] | Помечает патч для бэкпортов в ветки LTS |
Reported-by: | Атрибуция первооткрывателю |
Signed-off-by: | Сертификат происхождения разработчика — обязательно |
Уязвимые ядра охватывают Linux 4.14–7.0-rc, все 6.18.x до 6.18.22 и 6.19.x до 6.19.12.
Временное смягчение (перезагрузка не требуется на большинстве систем):
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Этот обходной путь не влияет на dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS или SSH.
xint.io/blog/copy-fail-linux-distributionsnvd.nist.gov/vuln/detail/CVE-2026-31431a664bf3d603d (mainline)72548b093ee3 (Linux 4.14, 2017)