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

User namespaces появились в ядре Linux 3.8 в 2013 году. Создать их может любой непривилегированный пользователь, вызвав unshare(2) или clone(2) с флагом CLONE_NEWUSER. Внутри нового пространства имён процесс получает полный набор capabilities, включая CAP_SYS_ADMIN, CAP_NET_ADMIN и всё остальное. Снаружи эти capabilities ничего не значат: они ограничены своим namespace и не дают власти над хостовой системой. Теоретически.

Почему CAP_NET_ADMIN в user namespace стал универсальным ключом к ядру

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

User namespaces сломали это допущение. Теперь любой непривилегированный пользователь может создать user namespace, получить внутри него CAP_NET_ADMIN, и все подсистемы, которые проверяют эту capability через ns_capable() с привязкой к конкретному network namespace, сочтут его доверенным. Именно через этот механизм работает большинство эксплойтов, связанных с netfilter.

Убедиться, что user namespaces включены в системе:

cat /proc/sys/kernel/unprivileged_userns_clone   # Debian/Ubuntu
# или
sysctl user.max_user_namespaces

Посмотреть текущие capabilities процесса внутри нового namespace:

unshare --map-root-user capsh --print

Вывод покажет полный набор cap_net_admin, cap_sys_admin и прочих capabilities, доступных внутри изолированного пространства. Со стороны это выглядит как root. Именно это и привлекает атакующих.

CVE-2024-1086 и механика double-free через nf_tables

Наиболее показательный пример из последних лет, CVE-2024-1086, раскрытый в начале 2024 года. Уязвимость находилась в подсистеме netfilter, конкретно в модуле nf_tables, и представляла собой классический double-free: функция nft_verdict_init() допускала использование положительных значений в качестве кодов ошибок при hook-вердиктах. Когда NF_DROP выдавал код, похожий на NF_ACCEPT, функция nf_hook_slow() освобождала один и тот же объект дважды.

Казалось бы, уязвимость в netfilter, которая требует CAP_NET_ADMIN. Без user namespaces это был бы риск только для привилегированных процессов. Но именно user namespaces превратили её в универсальный локальный эксплойт. Цепочка атаки выглядела так: создаётся user namespace, внутри него через полученный CAP_NET_ADMIN активируется nf_tables, запускается double-free, через физическое сканирование памяти обходится KASLR, перезаписывается переменная modprobe_path, и на выходе атакующий получает root shell.

Уязвимость затрагивала ядра с версии 5.14 по 6.6 включительно. Патч вышел в феврале 2024 года. Проверить версию ядра и убедиться в наличии исправления:

uname -r
# Патч присутствует в: v5.15.149+, v6.1.76+, v6.6.15+

Временный митигейшн на системах, где nf_tables не нужен:

# RedHat/CentOS
echo 'blacklist nf_tables' >> /etc/modprobe.d/blacklist-nf_tables.conf
dracut -f && reboot

# Debian/Ubuntu
echo 'blacklist nf_tables' | sudo tee /etc/modprobe.d/blacklist-nf_tables.conf
sudo update-initramfs -u && reboot

OverlayFS и многолетняя история привилегий через монтирование

Если netfilter это один полюс проблемы, то OverlayFS внутри user namespaces, другой. История началась ещё с CVE-2015-1328, когда обнаружилось, что Ubuntu допускает монтирование overlayfs внутри непривилегированного user namespace, и при копировании файлов некорректно проверялись права. Это позволяло создать setuid-root бинарник, доступный снаружи namespace. Upstream-ядро тогда не было затронуто, поскольку не разрешало непривилегированное монтирование overlayfs вовсе.

CVE-2021-3493 воспроизвёл тот же класс проблем на Ubuntu с ядрами ниже 5.11: overlayfs некорректно проверял применение filesystem capabilities относительно user namespaces. CVE-2023-0386 зашёл с другой стороны и поразил уже upstream-ядро вплоть до 6.2: при копировании файлов с setuid-битом из fuse-filesystem в overlayfs не проверялось, имеет ли пользователь право устанавливать этот бит. Результат тот же: создание привилегированного исполняемого файла обычным пользователем.

Проверить уязвимость к CVE-2023-0386:

# Система уязвима если ядро < 6.2 и overlayfs доступен в user namespace
cat /proc/filesystems | grep overlay
uname -r

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

CVE-2022-24122 и проблема подсчёта ссылок в ucounts

CVE-2022-24122 иллюстрирует ещё один класс проблем, возникших именно из-за user namespaces: управление жизненным циклом объектов, связанных с namespace. В файле kernel/ucount.c объект ucounts мог пережить уничтожение своего namespace, потому что при его создании не инкрементировался счётчик ссылок на namespace через get_user_ns(). Атакующий управлял временем создания и уничтожения namespace, провоцируя use-after-free и получая возможность переписать критические структуры данных ядра.

Затронутые версии: с 5.14 по 5.16.4. Исправление добавило один вызов get_user_ns() в нужном месте. Аудит namespace-операций через auditd для обнаружения аномальной активности:

# Включить аудит системных вызовов, связанных с namespace
auditctl -a always,exit -F arch=b64 \
  -S clone -S unshare -S setns \
  -F key=namespace_ops
ausearch -k namespace_ops | tail -50

Мониторинг подозрительного порождения процессов с повышенными привилегиями:

# Через journald
journalctl -k --grep="userns|namespace" | tail -30

Ответ дистрибутивов и почему глобальное отключение не решает проблему

Перед администраторами встаёт закономерный вопрос: если user namespaces это такой широкий вектор атак, почему бы просто не отключить их? Технически это возможно:

# Временно
sysctl -w kernel.unprivileged_userns_clone=0

# Постоянно
echo "kernel.unprivileged_userns_clone=0" > /etc/sysctl.d/99-userns.conf
sysctl --system

Проблема в том, что user namespaces сегодня используются повсеместно в легитимных целях. Браузеры на базе Chromium используют их для sandbox рендерер-процессов. Podman и другие rootless-контейнерные среды полностью зависят от них. Flatpak использует их для изоляции приложений. Отключение user namespaces на рабочей станции разработчика фактически ломает значительную часть современного пользовательского ПО.

Ubuntu с версии 23.10 пошла другим путём: вместо глобального отключения они ввели AppArmor-ограничение, при котором создание user namespace разрешено только процессам с явно разрешающим AppArmor-профилем или с CAP_SYS_ADMIN в начальном namespace. Профиль по умолчанию для неизвестных процессов выглядит так:

# /etc/apparmor.d/unprivileged_userns
profile unprivileged_userns {
  audit deny capability,
  audit deny change_profile,
  allow mqueue,
  allow ptrace,
  allow userns,
}

Включить ограничение на Ubuntu 23.10 и новее:

sysctl -w kernel.apparmor_restrict_unprivileged_userns=1
sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=1

Проверить текущий статус ограничения:

sysctl kernel.apparmor_restrict_unprivileged_userns
aa-status | grep userns

Исследователи из Qualys в 2025 году нашли три способа обойти это ограничение на Ubuntu через aa-exec, через busybox с разрешающим профилем и через LD_PRELOAD. Что подтверждает: защита через список разрешённых приложений это постоянная гонка, а не окончательное решение.

Что делать с атакуемой поверхностью прямо сейчас

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

Первый уровень: ограничить доступность подсистем, которые регулярно становятся вектором. nf_tables не нужен на большинстве серверов, которые не выполняют фильтрацию пакетов:

echo 'install nf_tables /bin/false' >> /etc/modprobe.d/hardening.conf

Второй уровень: seccomp-профили для контейнеров должны блокировать unshare и clone с CLONE_NEWUSER, если приложению это не нужно. Проверить активные seccomp-профили для процесса:

cat /proc/<pid>/status | grep Seccomp
# 0 = нет, 1 = strict, 2 = filter

Третий уровень: мониторинг аномалий. Процесс, запущенный обычным пользователем, который порождает дочерние процессы с uid 0, должен немедленно попадать в алерт. Проверить uid_map для активных namespace:

find /proc -name uid_map 2>/dev/null | \
  xargs grep -l "0 " | \
  awk -F/ '{print $3}' | \
  xargs -I{} ps -p {} -o pid,user,cmd 2>/dev/null

Архитектурное противоречие, которое не исчезнет с выходом следующего патча

Честная оценка ситуации такова: user namespaces делают ровно то, для чего задуманы. Проблема не в реализации механизма как такового, а в несоответствии между моделью доверия, заложенной в десятки подсистем ядра, и новой реальностью, где capability не означает "доверенный процесс администратора", а означает "процесс, которому разрешили создать namespace".

Из 51 проанализированного эксплойта в известных базах уязвимостей 32 из них, которые должны были блокироваться механизмом Linux capabilities, успешно повышали привилегии именно через создание нового user namespace. Это не случайность и не совпадение. Это системный эффект от введения возможности, которая переопределила семантику capabilities для кода, написанного в других предположениях.

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