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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2023-2598 — Эксплуатация CVE-2023-2598 о io_uring | Kitploit
Инструменты/GitHubGitHub/spongebob-369/cve-2023-2598
Повышение привилегийАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubspongebob-369/cve-2023-2598

CVE-2023-2598

Эксплуатация CVE-2023-2598 о io_uring

Репозиторий
391 год назадЕщё не проверено

Популярное

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

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

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

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

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

CVE-2023-2598

Что такое io_uring?

​ io_uring — это интерфейс системных вызовов для Linux. На данный момент он поддерживает почти все системные вызовы, не только изначальные read() и write(). Он позволяет приложению инициировать системные вызовы, которые могут выполняться асинхронно.

Очереди отправки и завершения

В основе каждой реализации io_uring лежат два кольцевых буфера — очередь отправки (SQ) и очередь завершения (CQ). Эти кольцевые буферы являются общими для приложения и ядра.

Мы можем получить элемент очереди отправки (SQE), который описывает syscall, который должен быть выполнен с помощью io_uring_get_sqe. Затем приложение выполняет системный вызов io_uring_enter, чтобы сообщить ядру, что в очереди отправки есть работа, ожидающая выполнения.

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

Уязвимость

Функция io_sqe_buffer_register реализует отображение виртуальных страниц и физических адресов.

Сначала следует уточнить некоторые понятия.

Приложение инициирует запрос на буфер с помощью io_uring_register. Цепочка вызовов следующая:

io_uring_register_buffers->io_uring_register->io_sqe_buffers_register

Исходный код функции io_sqe_buffers_register:

int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg,
			    unsigned int nr_args, u64 __user *tags)
{
	struct page *last_hpage = NULL;
	struct io_rsrc_data *data;
	int i, ret;
	struct iovec iov;

	BUILD_BUG_ON(IORING_MAX_REG_BUFFERS >= (1u << 16));

	if (ctx->user_bufs)
		return -EBUSY;
	if (!nr_args || nr_args > IORING_MAX_REG_BUFFERS)
		return -EINVAL;
	ret = io_rsrc_node_switch_start(ctx);
	if (ret)
		return ret;
	ret = io_rsrc_data_alloc(ctx, io_rsrc_buf_put, tags, nr_args, &data);
	if (ret)
		return ret;
	ret = io_buffers_map_alloc(ctx, nr_args);
	if (ret) {
		io_rsrc_data_free(data);
		return ret;
	}

	for (i = 0; i < nr_args; i++, ctx->nr_user_bufs++) {
		if (arg) {
			ret = io_copy_iov(ctx, &iov, arg, i);
			if (ret)
				break;
			ret = io_buffer_validate(&iov);
			if (ret)
				break;
		} else {
			memset(&iov, 0, sizeof(iov));
		}

		if (!iov.iov_base && *io_get_tag_slot(data, i)) {
			ret = -EINVAL;
			break;
		}

		ret = io_sqe_buffer_register(ctx, &iov, &ctx->user_bufs[i],
					     &last_hpage);
		if (ret)
			break;
	}

	WARN_ON_ONCE(ctx->buf_data);

	ctx->buf_data = data;
	if (ret)
		__io_sqe_buffers_unregister(ctx);
	else
		io_rsrc_node_switch(ctx, NULL);
	return ret;
}

В этой функции мы перейдём к io_sqe_buffer_register. И там мы обнаружим логическую ошибку. Исходный код функции io_sqe_buffer_register:

static int io_sqe_buffer_register(struct io_ring_ctx *ctx, struct iovec *iov,
				  struct io_mapped_ubuf **pimu,
				  struct page **last_hpage)
{
	struct io_mapped_ubuf *imu = NULL;
	struct page **pages = NULL;
	unsigned long off;
	size_t size;
	int ret, nr_pages, i;
	struct folio *folio = NULL;

	*pimu = ctx->dummy_ubuf;
	if (!iov->iov_base)
		return 0;

	ret = -ENOMEM;
	pages = io_pin_pages((unsigned long) iov->iov_base, iov->iov_len,
				&nr_pages);
	if (IS_ERR(pages)) {
		ret = PTR_ERR(pages);
		pages = NULL;
		goto done;
	}

	/* Если это огромная страница, попробуйте объединить их в одну запись bvec */
	if (nr_pages > 1) {
		folio = page_folio(pages[0]);
		for (i = 1; i < nr_pages; i++) {
			if (page_folio(pages[i]) != folio) {
				folio = NULL;
				break;
			}
		}
		if (folio) {
			folio_put_refs(folio, nr_pages - 1);
			nr_pages = 1;
		}
	}

	imu = kvmalloc(struct_size(imu, bvec, nr_pages), GFP_KERNEL);
	if (!imu)
		goto done;

	ret = io_buffer_account_pin(ctx, pages, nr_pages, imu, last_hpage);
	if (ret) {
		unpin_user_pages(pages, nr_pages);
		goto done;
	}

	off = (unsigned long) iov->iov_base & ~PAGE_MASK;
	size = iov->iov_len;
	/* сохраняем исходный адрес для последующей проверки */
	imu->ubuf = (unsigned long) iov->iov_base;
	imu->ubuf_end = imu->ubuf + iov->iov_len;
	imu->nr_bvecs = nr_pages;
	*pimu = imu;
	ret = 0;

	if (folio) {
		bvec_set_page(&imu->bvec[0], pages[0], size, off);
		goto done;
	}
	for (i = 0; i < nr_pages; i++) {
		size_t vec_len;

		vec_len = min_t(size_t, size, PAGE_SIZE - off);
		bvec_set_page(&imu->bvec[i], pages[i], vec_len, off);
		off = 0;
		size -= vec_len;
	}
done:
	if (ret)
		kvfree(imu);
	kvfree(pages);
	return ret;
}

Здесь я упомяну лишь несколько важных моментов.

  1. imu означает виртуальный адрес/страницу.
  2. page означает физический адрес/страницу.
  3. folio — это много страниц, которые физически непрерывны; предотвращает ситуацию, когда вызывается функция и её параметр содержит страницу, но эта страница принадлежит непрерывному диапазону страниц, и мы не уверены, использовать ли всю страницу или одну страницу.
  4. struct iovec — просто структура, описывающая буфер с начальным адресом буфера и его длиной. Ничего больше.
  5. io_mapped_ubuf — это структура, содержащая информацию о буфере, зарегистрированном в экземпляре io_uring.
struct io_mapped_ubuf {
	u64		ubuf; // адрес, с которого начинается буфер
	u64		ubuf_end; // адрес, на котором он заканчивается
	unsigned int	nr_bvecs; // сколько bio_vec требуется для адресации буфера
	unsigned long	acct_pages;
	struct bio_vec	bvec[]; // массив bio_vec
};

Член bio_vec — это структура, похожая на iovec, но для физической памяти.

...
/* Если это огромная страница, попробуйте объединить их в одну запись bvec */
	if (nr_pages > 1) { // если больше одной страницы
		folio = page_folio(pages[0]); // преобразует страницу в folio
		// возвращает folio, содержащий эту страницу
		for (i = 1; i < nr_pages; i++) {
			if (page_folio(pages[i]) != folio) { // разные folio -> не физически непрерывны
				folio = NULL; // устанавливаем folio в NULL, так как не можем объединить в одну запись
				break;
			}
		}
		if (folio) { // если все страницы находятся в одном folio
			folio_put_refs(folio, nr_pages - 1);
			nr_pages = 1; // устанавливает nr_pages = 1, так как это можно представить как одну страницу folio
		}
	}
...

Код, проверяющий, все ли страницы из одного folio, на самом деле не проверяет, являются ли они последовательными. Это может быть одна и та же страница, отображённая несколько раз. Во время итерации page_folio(page) будет снова и снова возвращать один и тот же folio, проходя проверки. Это очевидная логическая ошибка. Продолжим с io_sqe_buffer_register и посмотрим, каковы последствия.

...
	imu = kvmalloc(struct_size(imu, bvec, nr_pages), GFP_KERNEL);
	// выделяет imu с массивом для nr_pages bio_vec
	// bio_vec — непрерывный диапазон физических адресов памяти
	// нам нужен bio_vec для каждой (физической) страницы
    // в случае folio массив bio_vec будет размером 1
	if (!imu)
		goto done;

	ret = io_buffer_account_pin(ctx, pages, nr_pages, imu, last_hpage);
	if (ret) {
		unpin_user_pages(pages, nr_pages);
		goto done;
	}

	off = (unsigned long) iov->iov_base & ~PAGE_MASK;
	size = iov->iov_len; // устанавливает размер равным переданному пользователем!
	/* сохраняем исходный адрес для последующей проверки */
	imu->ubuf = (unsigned long) iov->iov_base; // контролируется пользователем
	imu->ubuf_end = imu->ubuf + iov->iov_len; // вычисляет конец на основе длины
	imu->nr_bvecs = nr_pages; // будет 1 в случае folio
	*pimu = imu;
	ret = 0;

	if (folio) { // в случае folio — нужен только один bio_vec (эффективно!)
		bvec_set_page(&imu->bvec[0], pages[0], size, off);
		goto done;
	}
	for (i = 0; i < nr_pages; i++) {
		size_t vec_len;

		vec_len = min_t(size_t, size, PAGE_SIZE - off);
		bvec_set_page(&imu->bvec[i], pages[i], vec_len, off);
		off = 0;
		size -= vec_len;
	}
done:
	if (ret)
		kvfree(imu);
	kvfree(pages);
	return ret;
}

Выделяется один bio_vec, так как nr_pages = 1. Размер буфера, записываемый в pimu->iov_len и pimu->bvec[0].bv_len, равен тому, который передал пользователь в iov->iov_len.

Эксплуатация

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