[PATCH] test/recv-bundle-pbuf-len-poison: add regression test for pbuf len corruption

Nyakundi Emmanuel <[email protected]>
Newsgroups org.kernel.vger.io-uring,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
A failed IORING_RECVSEND_BUNDLE receive on a non-INC provided-buffer
ring can persistently corrupt the buffer descriptor length. When the
receive fails with -EAGAIN, the kernel writes the requested length into
buf->len during buffer selection but never restores it on failure.

A later unrelated IORING_OP_READ using the same buffer group then
consumes the corrupted length, returning fewer bytes than expected.

This test reproduces the issue as reported by Federico Brasili.

Reported-by: Federico Brasili <[email protected]>
Link: https://lore.kernel.org/io-uring/CAAEr8jbY60noGj1fw_k91UJRBkyiRVoS6=nLhZ7Svwidjn4CAA@mail.gmail.com/
Signed-off-by: Nyakundi Emmanuel <[email protected]>
---
 test/recv-bundle-pbuf-len-poison.c | 146 +++++++++++++++++++++++++++++
 1 file changed, 146 insertions(+)
 create mode 100644 test/recv-bundle-pbuf-len-poison.c

diff --git a/test/recv-bundle-pbuf-len-poison.c b/test/recv-bundle-pbuf-len-poison.c
new file mode 100644
index 00000000..90fafff4
--- /dev/null
+++ b/test/recv-bundle-pbuf-len-poison.c
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Regression test for io_uring provided-buffer ring length corruption.
+ *
+ * A failed IORING_RECVSEND_BUNDLE receive on a non-INC provided-buffer
+ * ring can persistently shrink the user-visible buffer descriptor length.
+ * The modified length is not rolled back when the receive fails with
+ * -EAGAIN, and a later unrelated IORING_OP_READ from a pipe consumes
+ * the corrupted length.
+ *
+ * Reported-by: Federico Brasili <[email protected]>
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include "liburing.h"
+#include "helpers.h"
+
+#define BGID		8
+#define BUF_SIZE	4096
+#define NR_BUFS		2
+
+static int test(void)
+{
+	struct io_uring_buf_ring *br;
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	struct io_uring ring;
+	struct io_uring_buf *buf_entry;
+	int sockfd, pipefds[2], ret;
+	void *buf;
+	char pipe_data[BUF_SIZE];
+
+	ret = io_uring_queue_init(8, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "queue init failed: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	if (posix_memalign(&buf, 4096, BUF_SIZE * NR_BUFS))
+		return T_EXIT_FAIL;
+
+	/* set up non-INC provided buffer ring with 2 buffers of BUF_SIZE */
+	br = io_uring_setup_buf_ring(&ring, NR_BUFS, BGID, 0, &ret);
+	if (!br) {
+		if (ret == -EINVAL)
+			return T_EXIT_SKIP;
+		fprintf(stderr, "buf ring setup failed: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+
+	io_uring_buf_ring_add(br, buf,             BUF_SIZE, 0, NR_BUFS - 1, 0);
+	io_uring_buf_ring_add(br, buf + BUF_SIZE,  BUF_SIZE, 1, NR_BUFS - 1, 1);
+	io_uring_buf_ring_advance(br, NR_BUFS);
+
+	/* create an empty SOCK_DGRAM socket to trigger -EAGAIN */
+	sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (sockfd < 0) {
+		perror("socket");
+		return T_EXIT_FAIL;
+	}
+
+	/* submit RECV_BUNDLE on empty socket — expects -EAGAIN */
+	sqe = io_uring_get_sqe(&ring);
+	io_uring_prep_recv(sqe, sockfd, NULL, 1, MSG_DONTWAIT);
+	sqe->ioprio |= IORING_RECVSEND_BUNDLE;
+	sqe->flags  |= IOSQE_BUFFER_SELECT;
+	sqe->buf_group = BGID;
+	sqe->user_data  = 0x1111;
+	io_uring_submit(&ring);
+
+	ret = io_uring_wait_cqe(&ring, &cqe);
+	if (ret) {
+		fprintf(stderr, "wait cqe failed: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+	if (cqe->res != -EAGAIN) {
+		fprintf(stderr, "expected -EAGAIN, got %d\n", cqe->res);
+		io_uring_cqe_seen(&ring, cqe);
+		return T_EXIT_FAIL;
+	}
+	io_uring_cqe_seen(&ring, cqe);
+
+	/* check entry0.len — must still be BUF_SIZE after failed RECV */
+	buf_entry = &br->bufs[0];
+	if (buf_entry->len != BUF_SIZE) {
+		fprintf(stderr,
+			"FAIL: entry0.len corrupted after -EAGAIN RECV_BUNDLE: "
+			"got %u, expected %u\n",
+			buf_entry->len, BUF_SIZE);
+		return T_EXIT_FAIL;
+	}
+
+	/* now do a pipe READ using the same buffer group */
+	if (pipe(pipefds)) {
+		perror("pipe");
+		return T_EXIT_FAIL;
+	}
+
+	memset(pipe_data, 'A', BUF_SIZE);
+	if (write(pipefds[1], pipe_data, BUF_SIZE) != BUF_SIZE) {
+		fprintf(stderr, "pipe write failed\n");
+		return T_EXIT_FAIL;
+	}
+
+	sqe = io_uring_get_sqe(&ring);
+	io_uring_prep_read(sqe, pipefds[0], NULL, BUF_SIZE, 0);
+	sqe->flags    |= IOSQE_BUFFER_SELECT;
+	sqe->buf_group = BGID;
+	sqe->user_data  = 0x6666;
+	io_uring_submit(&ring);
+
+	ret = io_uring_wait_cqe(&ring, &cqe);
+	if (ret) {
+		fprintf(stderr, "wait read cqe failed: %d\n", ret);
+		return T_EXIT_FAIL;
+	}
+	if (cqe->res != BUF_SIZE) {
+		fprintf(stderr,
+			"FAIL: READ got %d bytes, expected %d — "
+			"pbuf len was poisoned by failed RECV_BUNDLE\n",
+			cqe->res, BUF_SIZE);
+		io_uring_cqe_seen(&ring, cqe);
+		return T_EXIT_FAIL;
+	}
+	io_uring_cqe_seen(&ring, cqe);
+
+	close(sockfd);
+	close(pipefds[0]);
+	close(pipefds[1]);
+	io_uring_queue_exit(&ring);
+	free(buf);
+	return T_EXIT_PASS;
+}
+
+int main(int argc, char *argv[])
+{
+	if (argc > 1)
+		return T_EXIT_SKIP;
+
+	return test();
+}
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.