LIMB
Вход Регистрация

тема

Расширение окон гонок ядра с использованием /dev/shm[/B]

Logman · · 0 ответов · 101 просмотров

Недавно я наткнулся на один сабмишен для kernelCTF, в котором автор упоминает новую технику расширения окон гонок в ядре Linux:

kCTF submission

Не удалось прогрузить медиа


Позже я узнал, что на самом деле эта техника не является новой — она уже была описана в блоге от Starlabs. В этом посте также объясняется немного больше деталей, чем в сабмишене kernelCTF, о том, как именно можно использовать эту технику.

Однако ни один из этих двух ресурсов не дал мне полного понимания. Мне хотелось действительно разобраться, как именно работает эта техника… И именно этим я в итоге и занялся!

Это будет короткий и простой пост в блоге (в основном для себя), в котором я опишу то, что узнал.

Содержание


  • []PoC и TL;DR
    [
    ]Введение в /dev/shm
    []Выделение страниц с помощью fallocate()
    [
    ]Освобождение страниц с помощью FALLOC_FL_PUNCH_HOLE
    []Блокирование других потоков
    [
    ]Расширение окон гонок
  • Заключение

PoC и TL;DR

Вот минимальный PoC, который я написал, чтобы продемонстрировать эту технику на Ubuntu 25.04 (kernel 6.14.11). Закомментируйте второй вызов fallocate(), чтобы увидеть разницу во времени доступа.

Краткое объяснение:


  • []Открытие /dev/shm и передача файлового дескриптора в fallocate() приводит к вызову shmem_fallocate().
    [
    ]Флаг FALLOC_FL_PUNCH_HOLE заставляет shmem_fallocate() размэпить диапазон адресов, выделенный первым fallocate(), а также освободить физические страницы обратно системе. Это занимает заметное время.
    []Если во время этого процесса происходит page fault (например, при доступе к fmap), вызывается shmem_fault().
    [
    ]Если shmem_fault() обнаруживает, что выполняется hole punch, он вызывает shmem_falloc_wait() и усыпляет поток до завершения операции.
  • Если поток ядра попадает в shmem_fault() во время hole punch, он будет спать значительно дольше обычного page fault, что позволяет расширять окна гонок.

Введение в /dev/shm

В Linux /dev/shm — это файловая система в оперативной памяти, предназначенная для низколатентного временного хранения данных. Она также широко используется для IPC через разделяемую память.

В рамках этого поста её можно рассматривать как обычный файл. Его можно открыть и отобразить в память процесса для чтения и записи. Ключевое отличие заключается в том, что page fault’ы и некоторые системные вызовы обрабатываются специализированными обработчиками /dev/shm.

Выделение страниц с помощью fallocate()

После открытия /dev/shm и отображения страницы физическая память ещё не выделена. Реальное выделение происходит только при первом доступе к странице, что приводит к page fault.

Для /dev/shm обработкой page fault занимается shmem_fault(), которая вызывает shmem_get_folio_gfp() — функция, которая ищет страницу в кэше, подкачке или выделяет новую.

Существуют ситуации, в которых page fault может завершиться неудачей, например при нехватке памяти или дискового пространства, что может привести к OOM.

Для более контролируемого поведения используется fallocate(), который немедленно выделяет физические страницы и возвращает ошибку в userland при невозможности выполнить операцию.

Для /dev/shm этот вызов обрабатывается функцией shmem_fallocate().

Освобождение страниц с помощью FALLOC_FL_PUNCH_HOLE

fallocate() также позволяет освобождать ранее выделенные страницы с помощью флага FALLOC_FL_PUNCH_HOLE(обязательно вместе с FALLOC_FL_KEEP_SIZE).

Во время hole punch происходят следующие действия:


  • []В inode->i_private записывается маркер, указывающий, что hole punch активен.
    [
    ]Вызывается unmap_mapping_range(), который удаляет PTE и размэпливает страницы во всех процессах.
    []Вызывается shmem_truncate_range(), освобождающий физические страницы.
    [
    ]Все ожидающие потоки пробуждаются с помощью wake_up_all().

После этого любые обращения к этим адресам снова будут обрабатываться через обычный page fault.

Блокирование других потоков

Если во время hole punch другой поток сталкивается с page fault, shmem_fault() проверяет inode->i_private. Если он установлен, поток усыпляется через shmem_falloc_wait() до завершения hole punch.

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

Расширение окон гонок

Итоговые наблюдения:


  • []Page fault’ы для /dev/shm обрабатываются shmem_fault().
    [
    ]Страницы можно как выделять, так и освобождать через fallocate().
    []Page fault во время hole punch приводит к засыпанию потока.
    [
    ]Это можно использовать для искусственного расширения окон гонок в ядре.

Обязательное условие: внутри окна гонки должен происходить доступ к userland-адресу, например через copy_from_user() или copy_to_user().

Типовой сценарий использования:


  1. []Открыть /dev/shm и выделить максимальный объём памяти через fallocate() (64 МБ на Ubuntu 25.04).
    [
    ]В потоке T1 войти в уязвимое окно гонки.
    []В потоке T2 вызвать fallocate() с FALLOC_FL_PUNCH_HOLE.
    [
    ]Если T1 попадает на copy_to_user() / copy_from_user() во время hole punch, он засыпает, тем самым расширяя окно гонки.

Заключение

PoC из раздела PoC и TL;DR легко адаптируется под большинство race-condition эксплойтов в Linux. Достаточно заменить доступ к fmap на действие, которое приводит ядро во внутреннее уязвимое окно гонки.
👍4 😮1 🤔1

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.