Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2022-0847 — Эксплойт proof-of-concept и подробный анализ уязвимости повышения привилегий в ядре Linux CVE-2022-0847 (Dirty Pipe), включая среду Docker для тестирования и отладки. | Kitploit
Инструменты/GitHubGitHub/chenaotian/cve-2022-0847
Повышение привилегийАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных ФайловЛаборатории и Практика
GitHubchenaotian/cve-2022-0847

CVE-2022-0847

Эксплойт proof-of-concept и подробный анализ уязвимости повышения привилегий в ядре Linux CVE-2022-0847 (Dirty Pipe), включая среду Docker для тестирования и отладки.

Репозиторий
257664 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2022-0847 Dirty Pipe: анализ повышения привилегий в ядре Linux

[toc]

Статья впервые опубликована в официальном аккаунте Huawei Security; это блог-версия (более полная).

Ссылка на оригинал: https://mp.weixin.qq.com/s/6VhWBOzJ7uu80nzFxe5jpg

Краткие сведения об уязвимости

Идентификатор уязвимости: CVE-2022-0847 (псевдоним: Dirty Pipe / «грязная труба»)

Поражённый продукт: ядро Linux — системный вызов splice

Затронутые версии: начиная с Linux 5.8, где появился коммит f6dd975583bd, исправлено в 5.16.11, 5.15.25, 5.10.102

Ущерб: позволяет записать не более одной страницы в любой читаемый файл (этого достаточно) и локально повысить привилегии.

Подготовка окружения

Docker-образ для анализа уязвимости: chenaotian/cve-2022-0847 (если он всё ещё недоступен, значит, я ещё не загрузил его).

Предоставляет:

  • скомпилированное уязвимое ядро 5.13 с поддержкой отладки
  • qemu, gdb, исходный код ядра Linux 5.13
  • эксплоит

Запуск:

cd ~/cve-2022-0847
gcc exp.c -o exp --static && cp exp ./rootfs && cd rootfs
find . | cpio -o --format=newc > ../rootfs.img
cd ../ 
./boot.sh

Отладка:

gdb ./vmlinux
target remote :10086
directory /root/linux-5.13
b do_splice
b copy_page_to_iter_pipe 
b pipe_write
ignore 3 15
...
p *(struct pipe_inode_info *) pipe
p (struct pipe_buffer)pipe->bufs[0]

Принцип уязвимости

Кратко: вызов splice может отправить файл в pipe в режиме «нулевого копирования». На уровне кода такая «нулевая копия» означает, что страница файлового кэша (page cache) напрямую используется как страница buf канала pipe. При этом вводится неинициализированная переменная — из-за этой ошибки страница файлового кэша в дальнейшем обрабатывается как обычная страница кэша pipe, и в неё можно «дописать» данные, изменив её содержимое. Однако в этой ситуации ядро не помечает страницу кэша как «грязную», поэтому она не будет сброшена на диск в ближайшее время (до следующей перезагрузки и т.п.). В течение этого времени все обращения к файлу используют изменённую страницу файлового кэша, что даёт возможность «в течение короткого времени писать произвольные данные в любой читаемый файл». Это позволяет локально повысить привилегии.

Точка возникновения уязвимости

Судя по патчу, уязвимость находится в функции copy_page_to_iter_pipe: был добавлен код инициализации buf->flags. Это классическая ошибка неинициализированной переменной.

image-20220308170149137

Вызов copy_page_to_iter_pipe происходит внутри системного вызова splice. Функция splice (системный вызов) передаёт содержимое файла в канал методом «нулевого копирования». Это производительнее, чем традиционная передача содержимого файла в канал. Подробнее об этом ниже.

Принцип работы pipe и pipe_write

Для начала, раз уж уязвимость называют «грязной трубой», стоит разобраться с каналом (pipe). pipe — это коммуникационный механизм, предоставляемый ядром; он создаётся функциями pipe/pipe2 и возвращает два файловых дескриптора: один для отправки данных, другой для их приёма, как два конца трубы. Подробности использования расписывать не буду.

image-20220309124007780

Кратко о реализации в ядре: обычно пространство кэша pipe имеет общий размер 65536 байт и управляется страницами — всего 16 страниц (по 4096 байт). Страницы не обязательно непрерывны: они управляются через массив и образуют кольцевой список. Поддерживаются два указателя списка: один для записи (pipe->head), другой для чтения (pipe->tail). Здесь в основном разбирается функция pipe_write:

linux-5.13\fs\pipe.c : 400 : pipe_write

static ssize_t
pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
	struct file *filp = iocb->ki_filp;
	struct pipe_inode_info *pipe = filp->private_data;
	unsigned int head;
	ssize_t ret = 0;
	size_t total_len = iov_iter_count(from);
	ssize_t chars;
	bool was_empty = false;
	bool wake_next_writer = false;

	··· ···
    ··· ···
	head = pipe->head;
	was_empty = pipe_empty(head, pipe->tail);
	chars = total_len & (PAGE_SIZE-1);
	if (chars && !was_empty) { 
        //[1]pipe 缓存不为空,则尝试是否能从当前最后一页"接着"写
		unsigned int mask = pipe->ring_size - 1;
		struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask];
		int offset = buf->offset + buf->len; 

		if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
		    offset + chars <= PAGE_SIZE) { 
            /*[2]关键,如果PIPE_BUF_FLAG_CAN_MERGE 标志位存在,代表该页允许接着写
             *如果写入长度不会跨页,则接着写,否则直接另起一页 */
			ret = pipe_buf_confirm(pipe, buf);
			···
			ret = copy_page_from_iter(buf->page, offset, chars, from);
			···
			}
			buf->len += ret;
			···
		}
	}

	for (;;) {//[3]如果上一页没法接着写,则重新起一页
		··· ···
		head = pipe->head;
		if (!pipe_full(head, pipe->tail, pipe->max_usage)) {
			unsigned int mask = pipe->ring_size - 1;
			struct pipe_buffer *buf = &pipe->bufs[head & mask];
			struct page *page = pipe->tmp_page;
			int copied;

			if (!page) {//[4]重新申请一个新页
				page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
				if (unlikely(!page)) {
					ret = ret ? : -ENOMEM;
					break;
				}
				pipe->tmp_page = page;
			}

			spin_lock_irq(&pipe->rd_wait.lock);

			head = pipe->head;
			··· ···
			pipe->head = head + 1;
			spin_unlock_irq(&pipe->rd_wait.lock);

			/* Insert it into the buffer array */
			buf = &pipe->bufs[head & mask];
			buf->page = page;//[5]将新申请的页放到页数组中
			buf->ops = &anon_pipe_buf_ops;
			buf->offset = 0;
			buf->len = 0;
			if (is_packetized(filp))
				buf->flags = PIPE_BUF_FLAG_PACKET;
			else
				buf->flags = PIPE_BUF_FLAG_CAN_MERGE;
            	//[6]设置flag,默认PIPE_BUF_FLAG_CAN_MERGE
			pipe->tmp_page = NULL;

			copied = copy_page_from_iter(page, 0, PAGE_SIZE, from); 
            //[7]拷贝操作
			··· ···
			ret += copied;
			buf->offset = 0;
			buf->len = copied;

			··· ···
		}
        ··· ···
    }
	··· ···
	return ret;
}
  1. Если текущий канал pipe не пуст (head == tail означает пустой канал), значит в нём есть непрочитанные данные. Берётся указатель head, то есть адрес самой свежей страницы для записи, и проверяются len, offset этой страницы (чтобы найти конец данных). Затем предпринимается попытка дописать данные в текущую страницу.
  2. Проверяется, есть ли у текущей страницы флаг PIPE_BUF_FLAG_CAN_MERGE; если его нет, дописывать в текущую страницу нельзя. Также проверяется, что после дописывания к существующим данным общая длина не превышает одну страницу (то есть запись не пересекает границу страницы). Если пересекает — дописать нельзя.
  3. Если в предыдущую страницу дописать нельзя, берётся новая страница.
  4. alloc_page выделяет новую страницу.
  5. Новая страница помещается в начало массива (может заменить существующую страницу), инициализируются значения.
  6. buf->flag по умолчанию устанавливается в PIPE_BUF_FLAG_CAN_MERGE, потому что по умолчанию странице разрешено дописывание.
  7. Копируются записываемые данные; если скопировано не всё, описанные операции повторяются.

Ключ к эксплуатации — неинициализированный флаг PIPE_BUF_FLAG_CAN_MERGE, оставшийся в splice. Именно он определяет, можно ли дописать данные в «незакрытую» страницу pipe.

От splice до copy_page_to_iter_pipe

Как уже упоминалось, pipe использует 16 страниц в качестве кольцевого кэша. Метод нулевого копирования в splice состоит в том, что страница файлового кэша напрямую подставляется вместо страницы кэша pipe (указатель страницы в pipe меняется на страницу файлового кэша).

image-20220309124515813

Скачать инструмент