[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