
Эксплуатация CVE-2022-0847 - автор: Antonius (w1sdom)
Dirty Pipe (CVE-2022-0847) — одна из самых значительных уязвимостей безопасности в ядре Linux версий 5.8 – 5.15.24, обнаруженная Максом Келлерманном в 2022 году. Эта уязвимость позволяет обычным пользователям (без специальных привилегий) перезаписывать данные в файлах, которые должны быть доступны только для чтения. Понимание основных концепций
Прежде чем подробно обсуждать Dirty Pipe, необходимо понять некоторые внутренние концепции ядра Linux:
1. Страничная организация памяти (Paging)
Страничная организация памяти — это механизм управления памятью в ядре Linux, при котором система памяти делит физическую память на небольшие блоки фиксированного размера, называемые кадрами страниц (page frames), а виртуальная память делится на блоки того же размера, называемые страницами (pages).
Этот механизм позволяет ядру отображать виртуальное адресное пространство процессов на физическую память не последовательным образом, что критически важно для эффективности и безопасности в современных системах.
2. Страница (Виртуальная память)
В Linux страница — это наименьшая единица управления физической памятью, обрабатываемая ядром.
Аналогия: ОЗУ — это огромная книга. Страница — один лист бумаги в этой книге. Ядро не перемещает данные бит за битом, а лист за листом (страница за страницей).
Обычно в современных архитектурах (например, x86_64) стандартный размер одной страницы составляет 4 КБ (4096 байтов).
3. Кэш страниц (Page Cache)
Это ключевая часть. Linux не читает файлы напрямую с диска каждый раз, потому что это медленно. Ядро копирует содержимое файлов в ОЗУ, которое называется кэшем страниц (Page Cache).
4. Буфер канала (Pipe Buffer)
Канал — это механизм межпроцессного взаимодействия (IPC). Внутренне ядро управляет каналами с помощью структуры данных pipe_inode_info. Данные внутри канала хранятся в «буфере», называемом буфером канала (Pipe Buffer).
5. Флаг буфера канала (PIPE_BUF_FLAG_CAN_MERGE)
Флаг PIPE_BUF_FLAG_CAN_MERGE был введён в ядре Linux версии 5.8.
Именно здесь кроется основная уязвимость. Флаг называется PIPE_BUF_FLAG_CAN_MERGE.
6. Splice
splice() — это системный вызов для перемещения данных между двумя файловыми дескрипторами без копирования данных между пространством ядра и пользовательским пространством. Это часто называют механизмом нулевого копирования (Zero-copy).
Системный вызов splice() является «главным действующим лицом» в Dirty Pipe:
7. Копирование при записи (Copy on Write, CoW)
Механизм копирования при записи (CoW) — это стратегия оптимизации управления памятью, используемая ядром Linux для отсрочки копирования данных до тех пор, пока это не станет абсолютно необходимым.
Связь между копированием при записи (CoW) и эксплойтом Dirty Pipe (CVE-2022-0847) заключается в том, как небольшая ошибка в ядре Linux успешно «обманывает» механизм CoW, позволяя записывать данные в файлы, которые должны быть доступны только для чтения.
8. Грязная страница (Dirty Page)
Грязная страница — это страница памяти в ОЗУ, которая была изменена приложением, но изменения ещё не были записаны обратно на вторичное хранилище (например, SSD или жёсткий диск).
Анализ уязвимости Dirty Pipe
Dirty Pipe — это тип логической ошибки (logic bug) в обработке буфера канала в ядре Linux версий 5.8 – 5.15.24. Основная проблема заключается в механизме каналов (канал межпроцессного взаимодействия) и в том, как ядро управляет кэшем страниц (память, хранящая копии данных файлов с диска). Ключевая проблема — ошибка во флаге PIPE_BUF_FLAG_CAN_MERGE.
Основная проблема заключается в том, что ядро не смогло должным образом повторно инициализировать этот флаг (логическая ошибка). Вот анализ кода: В функциях copy_page_to_iter_pipe и push_to_pipe в ядре Linux до версии 5.16.11 при выполнении операций splice ядро подготавливает структуру pipe_buffer, но забывает очистить поле .flags.
Уязвимая структура кода:
// Местоположение проблемы: fs/pipe.c или include/linux/pipe_fs_i.h
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags; // <--- ЭТОТ ФЛАГ НЕ СБРАСЫВАЕТСЯ
unsigned long private;
};
Код до патча (уязвимый):
// lib/iov_iter.c - До патча CVE-2022-0847
static size_t copy_page_to_iter_pipe(struct page *page,
size_t offset, size_t bytes, struct iov_iter *i) {
// ---------пропуск-----------
struct pipe_buffer *buf = &pipe->bufs[head & mask];