
Эксплойт локального повышения привилегий для CVE-2023-0386, нацеленный на overlayfs ядра Linux. Включает подробный анализ уязвимости, код PoC и пошаговое руководство по эксплуатации с использованием FUSE и пользовательских пространств имен.
gcc -Wall exp.c `pkg-config fuse --cflags --libs` -o exp
./exp /tmp

Теоретические знания (пространства имён, overlay-файловая система, FUSE-файловая система и т.д.) в этой статье взяты из ChatGPT.
Идентификатор уязвимости: CVE-2023-0386
Уязвимый продукт: ядро Linux – overlay-файловая система
Затрагиваемые версии: 5.11 – 5.19
Условия эксплуатации: возможность выполнить unshare или создать overlay-файловую систему
Эффект от эксплуатации: локальное повышение привилегий
Сборка собственного ядра:
Подготовьте версию из диапазона уязвимых, исключая версию 5.15 (похоже, в ней есть подводные камни), включите две файловые системы overlay и fuse:
CONFIG_SLUB_DEBUGOVERLAY_FS
CONFIG_FUSE_FS
Проверено на Ubuntu 21.10 с версией ядра 5.13.0-16-generic – работает:

Перед анализом уязвимости давайте попросим ChatGPT сыграть роль эксперта по ядру Linux:
(Запрос ChatGPT: теперь ты будешь играть роль эксперта по ядру Linux, помоги мне ответить на некоторые вопросы)
Публичная информация об уязвимости скудна, наиболее очевидным является информация о патче, ссылка на патч:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f11ada10d0a

Видно, что в функцию ovl_copy_up_one добавлена проверка. Сначала спросим ChatGPT, что делает эта функция:

Таким образом, эта функция выполняется в процессе копирования файла из нижнего слоя overlay-файловой системы в верхний. Теперь, в контексте, рассмотрим новую проверку, добавленную патчем:
static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
int flags)
{
int err;
DEFINE_DELAYED_CALL(done);
struct path parentpath;
struct ovl_copy_up_ctx ctx = {
.parent = parent,
.dentry = dentry,
.workdir = ovl_workdir(dentry),
};
if (WARN_ON(!ctx.workdir))
return -EROFS;
ovl_path_lower(dentry, &ctx.lowerpath);
err = vfs_getattr(&ctx.lowerpath, &ctx.stat,//[1] Получить stat нижней файловой системы
STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
if (err)
return err;
//[2] Новая проверка патча: есть ли отображение UID и GID файла в текущем пространстве имён
if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
!kgid_has_mapping(current_user_ns(), ctx.stat.gid))
return -EOVERFLOW;
[1] Сначала с помощью vfs_getattr получает атрибуты целевого файла из нижней файловой системы. Функция vfs_getattr принимает struct path файла и возвращает соответствующую struct stat.
[1.1] ctx.lowerpath – путь к файлу в нижней файловой системе overlay. Overlay-файловая система будет описана ниже.
[1.2] struct stat хранит метаданные файла, включая владельца и группу. Полученная информация о владельце затем проверяется в новой проверке патча.
[2] Затем вызывается kuid_has_mapping для проверки владельца и группы только что полученного файла. Проверяется, отображены ли владелец и группа целевого файла в текущем пространстве имён пользователя.
[2.1] kuid_has_mapping принимает два параметра: struct user_namespace и struct kuid. Функция проверяет, отображён ли заданный пользователь в заданном пространстве имён. Подробнее об отображении пользователей в пространствах имён будет рассказано ниже.
Итак, мы знаем, что теперь при выполнении операции функции с уязвимостью (ovl_copy_up_one), если владелец или группа целевого нижнего файла не отображены в текущем пространстве имён, операция завершается неудачей.
Таким образом, принцип патча ясен, но для воспроизведения уязвимости нам всё ещё нужно решить следующие вопросы:
ovl_copy_up_one, т.е. копирование файла из нижнего слоя overlay-файловой системы в верхний?lowerpath, для которого проверяется отображение владельца?Прежде чем ответить на эти вопросы, необходимо разобраться с некоторыми базовыми понятиями:
(Запрос ChatGPT: расскажите o пространствах имён в ядре Linux)
В Linux пространства имён (namespaces) — это функция ядра, используемая для изоляции ресурсов. Пространства имён позволяют группе процессов выглядеть так, как будто они работают в независимой среде, повышая безопасность и управляемость системы. Пространства имён играют ключевую роль в технологиях контейнеризации (например, Docker), позволяя контейнерам работать в изолированной среде, не влияя на другие контейнеры или основную систему.
Ядро Linux поддерживает 7 типов пространств имён (mount, pid, net, ipc, user, time, cgroup), каждое из которых изолирует определённый класс системных ресурсов. Пространства имён создаются, изменяются и управляются с помощью системных вызовов (clone, unshare, setns). Среды выполнения контейнеров (например, Docker) и другие инструменты виртуализации используют эти пространства имён для обеспечения независимой, изолированной среды выполнения для контейнеров.
Функция проверки kuid_has_mapping, добавленная патчем уязвимости, относится к пространству имён пользователя (user namespace) из вышеупомянутых семи.
(Запрос ChatGPT: расскажите о пространстве имён пользователя)
Пространство имён пользователя (User Namespace) изолирует идентификаторы пользователей (UID) и групп (GID). С его помощью можно использовать независимые наборы UID и GID в разных пространствах имён. Это означает, что пользователь или группа в одном пространстве имён могут иметь другие ID или права в другом пространстве имён. Пространство имён пользователя повышает безопасность и управляемость системы, особенно в контейнерных средах.
Ключевая особенность пространства имён пользователя — отображение ID: оно позволяет отображать UID и GID из одного пространства имён в другое. То есть одни и те же UID и GID могут представлять разных пользователей и группы в разных пространствах имён. Например, пользователь root (UID 0) в контейнере может быть отображён на непривилегированного пользователя в основной системе.
Запомним следующее:
/proc/[pid]/uid_map; /proc/[pid]/gid_map), для этой операции обычно требуются права root в начальном пространстве имён.Например, я создаю новое пространство имён пользователя от пользователя breeze, затем в этом новом пространстве просматриваю файл, владельцем которого является root, и вижу, что владелец отображается как nobody:

Это происходит потому, что в новом пространстве имён root — это пользователь breeze, создавший пространство, а root из начального пространства не был мной вручную отображён в новое пространство, поэтому он распознаётся как nobody.