Исследователи безопасности из компании Qualys раскрыли детали двух уязвимостей, затрагивающих ядро Linux и системный менеджер systemd. Уязвимость в ядре (CVE-2021-33909) позволяет локальному пользователю добиться выполнения кода с правами root через манипуляции с каталогами большой вложенности.
Опасность уязвимости усугубляется тем, что исследователям удалось подготовить рабочие эксплоиты, работающие в Ubuntu 20.04/20.10/21.04, Debian 11 и Fedora 34 в конфигурации по умолчанию. Отмечается, что другие дистрибутивы не проверялись, но теоретически тоже подвержены проблеме и могут быть атакованы. Полный код эксплоитов обещают опубликовать после повсеместного устранения проблемы, а пока доступен лишь ограниченный в функциональности прототип, вызывающий крах системы. Проблема проявляется с июля 2014 года и затрагивает выпуски ядра начиная с 3.16. Исправление уязвимости было скоординировано с сообществом и принято в состав ядра 19 июля. Основные дистрибутивы уже сформировали обновления пакетов с ядром (Debian, Ubuntu, Fedora, RHEL, SUSE, Arch).
Уязвимость вызвана отсутствием проверки результата преобразования типа size_t в int перед выполнением операций в коде seq_file, осуществляющем создание файлов из последовательности записей. Отсутствие проверки может привести к записи в область вне границ буфера при создании, монтировании и удалении структуры каталогов с очень большим уровнем вложенности (размер пути более 1 ГБ). В итоге, атакующий может добиться записи 10-байтовой строки “https://deleted” со смещением “- 2 ГБ – 10 байт”, указывающим на область, непосредственно предшествующую выделенному буферу.
Подготовленный эксплоит требует для работы 5 ГБ памяти и 1 миллион свободных inode. Работа эксплоита сводится к созданию через вызов mkdir() иерархии из около миллиона вложенных каталогов для достижения размера файлового пути, превышающего 1 ГБ. Данный каталог монтируется через bind-mount в отдельном пространстве имён идентификаторов пользователей (user namespace), после чего запускается функция rmdir() для его удаления. Параллельно создаётся поток, загружающий небольшую eBPF-программу, который блокируется на стадии после проверки псевдокода eBPF, но до его JIT-компиляции.
В непривилегированном пространстве имён идентификаторов пользователей открывается файл /proc/self/mountinfo и начинается чтение длинного пути из каталога, примонтированного при помощи bind-mount, что приводит к записи строки “https://deleted” в область до начала буфера. Позиция для записи строки выбирается таким образом, что она перезаписывает инструкцию в уже проверенной, но ещё не скомпилированной программе eBPF.
Далее на уровне программы eBPF неконтролируемая запись вне буфера трансформируется управляемую возможность чтения и записи в другие структуры ядра при помощи техники btf и map_push_elem. В итоге, эксплоит определяет местоположение буфера modprobe_path[] в памяти ядра и перезаписывает в нём путь “/sbin/modprobe”, что позволяет инициировать запуск любого исполняемого файла с правами root в случае выполнения вызова request_module(), который выполняется, например, при создании сокета netlink.
Исследователями приводится несколько обходных методов защиты, которые эффективны только для конкретного эксплоита, но не устраняют саму проблему. Рекомендуется установить параметр “/proc/sys/kernel/unprivileged_userns_clone” в значение 0 для запрета монтирования каталогов в отдельном пространстве имён идентификаторов пользователей, а также “/proc/sys/kernel/unprivileged_bpf_disabled” в 1 для запрета загрузки программ eBPF в ядро.
Примечательно, что разбирая альтернативный вариант атаки, связанный с использованием механизма FUSE вместо bind-mound для монтирования большого каталога, исследователи натолкнулись на ещё одну уязвимость (CVE-2021-33910), затрагивающую системный менеджер systemd. Оказалось, что при попытке монтирования через FUSE каталога c размером пути, превышающим 8 МБ, в управляющем процессе инициализации (PID1) наступает исчерпание памяти стека и крах, который приводит систему в состояние “panic”.
Проблема связана с тем, что systemd отслеживает и разбирает содержимое /proc/self/mountinfo, и обрабатывает каждую точки монтирования в функции unit_name_path_escape(), в которой выполняется операция strdupa(), размещающая данные в стеке, а не в динамически выделяемой памяти. Так как максимальный размер стека ограничен через RLIMIT_STACK обработка слишком большого размера пути к точке монтирования приводит к краху процесса PID1 и остановке работы системы. Для атаки можно использовать простейший модуль FUSE в сочетании с использованием в качестве точки монтирования каталога с большим уровнем вложенности, размер пути в котором превышает 8 МБ.
Проблема проявляется начиная с systemd 220 (апрель 2015) и уже устранена в systemd и исправлена в дистрибутивах
(Debian, Ubuntu, Fedora, RHEL, SUSE, Arch). Примечательно, что в выпуске systemd 248 эксплоит не работает из-за ошибки в systemd, приводящей к сбою при обработке /proc/self/mountinfo. Также интересно, что в 2018 году возникла похожая ситуация и при попытке написать эксплоит к уязвимости CVE-2018-14634 в ядре Linux, исследователи Qualys натолкнулись на три критические уязимости в systemd.