Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Einreichen
ToolsExploitsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2023-2598 — Die Ausnutzung von CVE-2023-2598 bezüglich io_uring | Kitploit
Tools/GitHubGitHub/spongebob-369/cve-2023-2598
Privilege EscalationSchwachstellenanalyseExploitationLernen & BildungBinary-Exploitation
GitHubspongebob-369/cve-2023-2598

CVE-2023-2598

Die Ausnutzung von CVE-2023-2598 bezüglich io_uring

Repository anzeigen
39vor 1 JahrNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2023-2598

Was ist io_uring?

​ io_uring ist eine Systemaufrufschnittstelle für Linux. Sie unterstützt inzwischen fast alle Systemaufrufe, nicht nur read() und write wie anfänglich. Sie ermöglicht einer Anwendung, Systemaufrufe anzustoßen, die asynchron ausgeführt werden können.

Submission- und Completion-Queues

Im Kern jeder io_uring-Implementierung befinden sich zwei Ringpuffer – die Submission Queue (SQ) und die Completion Queue (CQ). Diese Ringpuffer werden zwischen Anwendung und Kernel gemeinsam genutzt.

Wir können einen Submission-Queue-Eintrag (SQE) erhalten, der einen syscall beschreibt, den io_uring_get_sqe ausführen soll. Die Anwendung führt dann einen io_uring_enter-Systemaufruf aus, um dem Kernel mitzuteilen, dass in der Submission Queue Arbeit auf ihre Verarbeitung wartet.

Nachdem der Kernel die Operation ausgeführt hat, legt er einen Completion-Queue-Eintrag (CQE) in den Ringpuffer der Completion Queue, der anschließend von der Anwendung konsumiert werden kann.

Schwachstelle

Die Funktion io_sqe_buffer_register implementiert die Abbildung virtueller Seiten auf physische Adressen.

Zunächst sollten wir einige Konzepte klären.

Die Anwendung initiiert eine Anforderung für einen Puffer über io_uring_register. Die Aufrufkette sieht wie folgt aus:

io_uring_register_buffers->io_uring_register->io_sqe_buffers_register

Der Quellcode der Funktion io_sqe_buffers_register ist wie folgt:

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;
}

In dieser Funktion stoßen wir auf io_sqe_buffer_register. Und wir werden einen logischen Fehler finden. Der Quellcode der Funktion io_sqe_buffer_register ist wie folgt:

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;
	}

	/* If it's a huge page, try to coalesce them into a single bvec entry */
	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;
	/* store original address for later verification */
	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;
}

Hier erwähne ich nur einige wichtige Punkte.

  1. imu bedeutet virtuelle Adresse/Seite.
  2. page bedeutet physische Adresse/Seite.
  3. folio bezeichnet viele Seiten, die physisch zusammenhängen. Es verhindert die Situation, dass beim Aufruf einer Funktion, deren Parameter eine Seite enthält, nicht klar ist, ob die gesamte Seite oder nur eine einzelne Seite verwendet werden soll, obwohl diese Seite zu einem zusammenhängenden Bereich von Seiten gehört.
  4. struct iovec -> nur eine Struktur, die einen Puffer beschreibt, mit der Startadresse des Puffers und seiner Länge. Mehr nicht.
  5. Ein io_mapped_ubuf ist eine Struktur, die die Informationen über einen Puffer enthält, der bei einer io_uring-Instanz registriert wurde.
struct io_mapped_ubuf {
	u64		ubuf; // the address at which the buffer starts
	u64		ubuf_end; // the address at which it ends
	unsigned int	nr_bvecs; // how many bio_vec(s) are needed to address the buffer 
	unsigned long	acct_pages;
	struct bio_vec	bvec[]; // array of bio_vec(s)
};

Das Mitglied bio_ver ist eine struct wie iovec, aber für physischen Speicher.

...
/* If it's a huge page, try to coalesce them into a single bvec entry */
	if (nr_pages > 1) { // if more than one page
		folio = page_folio(pages[0]); // converts from page to folio
		// returns the folio that contains this page
		for (i = 1; i < nr_pages; i++) {
			if (page_folio(pages[i]) != folio) { // different folios -> not physically contiguous 
				folio = NULL; // set folio to NULL as we cannot coalesce into a single entry
				break;
			}
		}
		if (folio) { // if all the pages are in the same folio
			folio_put_refs(folio, nr_pages - 1); 
			nr_pages = 1; // sets nr_pages to 1 as it can be represented as a single folio page
		}
	}
...

Der Code, der prüft, ob die Seiten aus demselben Folio stammen, prüft nicht tatsächlich, ob sie aufeinanderfolgend sind. Es kann dieselbe Seite sein, die mehrfach abgebildet wurde. Während der Iteration würde page_folio(page) immer wieder dasselbe Folio zurückgeben und die Prüfungen bestehen. Dies ist ein offensichtlicher Logikfehler. Fahren wir mit io_sqe_buffer_register fort und sehen, was die Konsequenzen sind.

...
	imu = kvmalloc(struct_size(imu, bvec, nr_pages), GFP_KERNEL); 
	// allocates imu with an array for nr_pages bio_vec(s)
	// bio_vec - a contiguous range of physical memory addresses
	// we need a bio_vec for each (physical) page
    // in the case of a folio - the array of bio_vec(s) will be of size 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; // sets the size to that passed by the user!
	/* store original address for later verification */
	imu->ubuf = (unsigned long) iov->iov_base; // user-controlled
	imu->ubuf_end = imu->ubuf + iov->iov_len; // calculates the end based on the length
	imu->nr_bvecs = nr_pages; // this would be 1 in the case of folio
	*pimu = imu;
	ret = 0;

	if (folio) { // in case of folio - we need just a single bio_vec (efficiant!)
		bvec_set_page(&imu->bvec[0], pages[0], size, off);
		goto done;
	}
	for (i = 0; i < nr_pages; i++) { 
		size_t vec_len;
Tool herunterladen