Уязвимость Copy Fail в Kubernetes: почему PSS Restricted и RuntimeDefault не защищают кэш ядра Linux
Контейнерная архитектура современных кластеров Kubernetes строится на предположении, что многопользовательская изоляция надежно обеспечивается комбинацией пространств имен, контрольных групп cgroups и стандартов безопасности Pod Security Standards (PSS). Инфраструктурные команды привыкли считать: если pod запущен без root-привилегий, с полным сбросом linux capabilities, флагом allowPrivilegeEscalation: false и стандартным профилем seccomp RuntimeDefault, его процессы изолированы от соседних нагрузок. Однако исследование уязвимости CVE-2026-31431 (Copy Fail) выявило фундаментальное ограничение этой модели: контейнеры делят между собой единый экземпляр ядра Linux и общее дисковое кэширование в оперативной памяти — page cache.
Парадокс контейнерной изоляции и разделяемый page cache
В отличие от аппаратных виртуальных машин с независимой памятью гостевых ОС, контейнеры в Kubernetes исполняются на одном общем ядре хоста. Для оптимизации файлового ввода-вывода операционная система использует page cache — буфер страниц оперативной памяти, в котором удерживаются данные недавно прочитанных или записанных файлов. Этот механизм работает на уровне системных объектов ядра, а не пространств имен контейнеров.
Если на одном узле запущены несколько подов, использующих общий базовый образ (например, Alpine или Debian), их неизменяемые слои OCI физически обслуживаются одними и теми же страницами в page cache. Несмотря на раздельные пространства имен монтирования (mount namespaces), операции чтения бинарных файлов и библиотек из общего слоя адресуются к идентичным физическим страницам оперативной памяти.
Анатомия CVE-2026-31431: сбой в сокетах AF_ALG
Уязвимость Copy Fail обнаружена в интерфейсе криптографических сокетов ядра Linux algif_aead, относящемся к адресному семейству AF_ALG (числовой код — 38). Интерфейс позволяет процессам в пользовательском пространстве обращаться к криптоалгоритмам ядра через стандартный сокетный API.
Дефект проявляется при комбинации операций аутентифицированного шифрования AEAD и системного вызова splice(), перемещающего данные между дескрипторами без копирования в пространство пользователя. Непривилегированный процесс способен через этот интерфейс записать данные непосредственно в страницы кэша памяти файла, открытого исключительно для чтения.
Ключевая особенность: ядро не помечает модифицированные страницы как «грязные» (dirty pages). Файловая система не инициирует сброс измененных байтов на диск, поэтому искажения существуют только в оперативной памяти ядра.
Расхождение памяти и диска: почему слепнут сканеры FIM
Механизм Copy Fail создает расхождение между состоянием накопителя и данными, считываемыми запущенными сервисами:
- Буферизованное чтение из кэша: Обычные процессы, вызывающие
read()илиmmap(), получают данные из page cache. Если страница искажена сокетом, процесс читает подмененные байты. - Прямой ввод-вывод с диска: При использовании флага
O_DIRECTзапрос направляется напрямую к блочному устройству в обход кэша, возвращая оригинальное содержимое. - Контрольные суммы: Средства мониторинга целостности файлов (FIM) и дисковые сканеры видят исходный хэш SHA-256 и не фиксируют признаков компрометации.
В результате сервис выполняет искаженный код из кэша памяти, тогда как аудиторские утилиты рапортуют о полной неизменности файлов на диске.
Практическое воспроизведение: тесты на Talos и AWS EKS
Воспроизводимость атаки была подтверждена исследователями Juliet Security в двух кластерных окружениях:
- Sidero Talos v1.12.2: Иммутабельная ОС с ядром
6.18.5-talosи средой containerd 2.1.6. - AWS EKS на базе Amazon Linux 2023.11: Платформа с ядром
6.12.79-101.147.amzn2023.x86_64и containerd 2.2.1.
В обоих тестах непривилегированный pod без capabilities под профилями PSS Restricted и RuntimeDefault seccomp успешно создал сокет AF_ALG и привязал алгоритм AEAD. В межконтейнерных тестах pod A изменил байты в файле разделяемого слоя образа, после чего pod B в отдельном пространстве имен на том же узле сразу увидел измененные данные. Монтирование каталогов хоста (hostPath) для атаки не требовалось.
Границы уязвимости: что останавливает PSS Restricted
Уязвимости присвоен рейтинг CVSS 3.1: 7.8 High с вектором AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, что указывает на ее строго локальный характер: атака требует выполнения кода на узле и не может быть проведена удаленно по сети.
В тестах с подменой бинарников с setuid-битом проявились границы защитных механизмов:
- В подах с флагом
allowPrivilegeEscalation: trueмодификация файла позволила процессу получить права root внутри контейнера. - В подах под контролем PSS Restricted параметр
allowPrivilegeEscalation: falseактивирует системный флагno_new_privs. Ядро заблокировало передачу повышенных привилегий при запуске измененного бинарника.
PSS Restricted успешно предотвращает повышение привилегий внутри пода, однако сам факт повреждения страниц в общем кэше памяти предотвратить не может.
Почему RuntimeDefault seccomp пропускает сокет
Стандартный профиль seccomp RuntimeDefault среды исполнения (containerd, Docker) фильтрует вызовы выборочно. Например, системный вызов socket(AF_VSOCK, ...) (семейство 40) в нем явно заблокирован, но вызов socket(AF_ALG, ...) (семейство 38) разрешен по умолчанию. Проверка наличия RuntimeDefault в конфигурации пода не гарантирует защиту от манипуляций с криптосокетами ядра.
Сравнение уровней защиты в Kubernetes
| Уровень изоляции | Реализуемые ограничения | Защита от CVE-2026-31431 |
|---|---|---|
| PSS Baseline | Запрещает явные привилегированные флаги и опасные тома | Уязвим; сокет AF_ALG открывается свободно |
| PSS Restricted | Требует non-root, no_new_privs и сброс capabilities | Блокирует root-эскалацию, но пропускает порчу кэша |
| RuntimeDefault seccomp | Фильтрует опасные системные вызовы среды исполнения | Пропускает socket(AF_ALG, ...) с кодом семейства 38 |
| Кастомный seccomp (Localhost) | Запрещает вызов socket с первым аргументом 38 | Полностью блокирует создание криптосокета (EPERM) |
| Обновление ядра Linux | Исправляет логику в подсистеме algif_aead | Устраняет первопричину уязвимости на узле |
Компенсирующий контроль: профиль seccomp Localhost
До установки исправлений ядра надежным барьером служит локальный профиль seccomp, возвращающий ошибку EPERM для вызова socket, если его первый аргумент равен 38:
- Доставка на узлы: Правила seccomp нельзя встроить в манифест пода. JSON-файл профиля должен быть размещен на каждом узле в каталоге kubelet (
/var/lib/kubelet/seccomp/). - Спецификация пода: В блоке безопасности пода задается
securityContext.seccompProfile.type: Localhostс путемlocalhostProfile: profiles/deny-af-alg.json. - Аудит приложений: Сервисы, законно использующие алгоритмы ядра через AF_ALG, потребуют предварительного согласования исключений.
Статус исправлений ядра и дистрибутивы ОС
Окончательное устранение дефекта требует обновления ядра Linux. В upstream-ветках исправление возвращает криптоподсистему к безопасной обработке буферов памяти («Revert to operating out-of-place»).
В дистрибутивах семейства RHEL криптодрайверы ядра могут компилироваться монолитно, поэтому правила blacklist algif_aead в /etc/modprobe.d/ не исключают вектор атаки. Для систем Debian, Ubuntu, SUSE и Amazon Linux требуется установка вендорных пакетов безопасности и последующая перезагрузка узлов.
Чек-лист для платформенных инженеров и SRE
- Инвентаризация узлов: Составьте реестр версий ядер и рантаймов. Отделите узлы с внешними или недоверенными сервисами.
- Аудит seccomp: Проверьте реальную активность профилей seccomp на хостах и протестируйте поведение вызова AF_ALG на staging.
- Плановый rollout нод: Сверьтесь с бюллетенями дистрибутива, подготовьте свежие образы хостов и проведите перезагрузку нод с проверкой версии ядра.
- Внедрение seccomp Localhost: На узлах, ожидающих обновления ядра, примените профиль с блокировкой AF_ALG через admission-контроллеры.
- Изоляция нагрузок: Разнесите недоверенные контейнеры и критические сервисы по разным группам узлов (node pools) через taints и nodeSelector.
- Процедуры расследования: При анализе инцидентов сопоставляйте данные буферизованного чтения с прямым чтением через флаг
O_DIRECT.
