#include "../config-host.h" /* SPDX-License-Identifier: MIT */ /* * Description: Verify recvmsg multishot with IOU_PBUF_RING_INC does not * return spurious -EFAULT (BADADDR) when the tail of a * partially-consumed buffer is smaller than the msghdr header * the kernel needs to place (sizeof(io_uring_recvmsg_out) + * namelen + controllen). * * The kernel must retire the too-small tail and advance to * the next ring entry rather than refusing the recv. * * Also validates the in-buffer layout of each CQE via the * io_uring_recvmsg_* helpers, so a bug that returns a valid * bid but mis-positions the payload is caught. * * See https://github.com/axboe/liburing/issues/1433 */ #include #include #include #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" #define BGID 1 #define NR_BUFS 4 #define BUF_SIZE 1024 #define QD 8 /* * hdr reserved per CQE by recvmsg multishot: * sizeof(io_uring_recvmsg_out) + namelen + controllen * = 16 + sizeof(struct sockaddr_in) + 0 = 32 * * Two send sizes exercise both buffer-advance paths: * * LARGE_SZ=480: two CQEs of (32+480)=512 consume bid=0 exactly. The * kernel advances to bid=1 via the normal head++ path, no retire * needed. * * SMALL_SZ=305: three CQEs of (32+305)=337 in a 1024-byte buffer total * 1011, leaving a 13-byte tail. 13 < 32, so the 4th send into that * buffer must retire the tail and advance to the next ring entry. * Pre-fix this manifests as -EFAULT; post-fix the CQE succeeds with * a new buffer id. * * Send sequence: 2 LARGE + 7 SMALL. Expected bid per CQE: * { 0, 0, 1, 1, 1, 2, 2, 2, 3 } * — one natural transition (0->1) and two retire transitions (1->2 * and 2->3). */ #define LARGE_SZ 480 #define NR_LARGE 2 #define SMALL_SZ 305 #define NR_SMALL 7 #define NR_SENDS (NR_LARGE + NR_SMALL) static const int expected_bids[NR_SENDS] = { 0, 0, 1, 1, 1, 2, 2, 2, 3 }; static int no_buf_ring, no_recv_mshot; static int setup_buf_ring(struct io_uring *ring, void **buf_mem, struct io_uring_buf_ring **out_br) { struct io_uring_buf_ring *br; struct io_uring_buf_reg reg = { }; size_t total = NR_BUFS * BUF_SIZE; int page_size = sysconf(_SC_PAGESIZE); size_t ring_size; void *mem; int ret, i; ring_size = NR_BUFS * sizeof(struct io_uring_buf); ring_size = (ring_size + page_size - 1) & ~(page_size - 1); mem = mmap(NULL, total, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) return -1; *buf_mem = mem; br = mmap(NULL, ring_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (br == MAP_FAILED) { munmap(mem, total); return -1; } *out_br = br; io_uring_buf_ring_init(br); reg.ring_addr = (unsigned long) br; reg.ring_entries = NR_BUFS; reg.bgid = BGID; reg.min_left = 32; ret = io_uring_register_buf_ring(ring, ®, IOU_PBUF_RING_INC); if (ret) { if (ret == -EINVAL) { no_buf_ring = 1; return 0; } fprintf(stderr, "register_buf_ring: %d\n", ret); return -1; } for (i = 0; i < NR_BUFS; i++) { io_uring_buf_ring_add(br, (char *)mem + i * BUF_SIZE, BUF_SIZE, i, io_uring_buf_ring_mask(NR_BUFS), i); } io_uring_buf_ring_advance(br, NR_BUFS); return 0; } static int test(void) { struct io_uring ring; struct io_uring_buf_ring *br = NULL; struct io_uring_cqe *cqe; struct io_uring_sqe *sqe; struct msghdr msg; struct sockaddr_in name; size_t expected_payload = NR_LARGE * LARGE_SZ + NR_SMALL * SMALL_SZ; uint8_t stream[NR_LARGE * LARGE_SZ + NR_SMALL * SMALL_SZ]; size_t bid_offset[NR_BUFS] = { 0 }; size_t stream_cursor = 0; size_t sent_offset = 0; void *buf_mem = NULL; int ret, fds[2]; int i, seen_bids = 0; int last_bid = -1; int ret_val = T_EXIT_FAIL; for (i = 0; i < (int) expected_payload; i++) stream[i] = (uint8_t)(i & 0xff); ret = io_uring_queue_init(QD, &ring, 0); if (ret) { fprintf(stderr, "queue_init: %d\n", ret); return T_EXIT_FAIL; } if (setup_buf_ring(&ring, &buf_mem, &br)) goto out; if (no_buf_ring) { ret_val = T_EXIT_SKIP; goto out; } ret = t_create_socket_pair(fds, true); if (ret) { fprintf(stderr, "socket_pair: %d\n", ret); goto out; } memset(&msg, 0, sizeof(msg)); msg.msg_name = &name; msg.msg_namelen = sizeof(name); sqe = io_uring_get_sqe(&ring); io_uring_prep_recvmsg_multishot(sqe, fds[0], &msg, 0); sqe->flags |= IOSQE_BUFFER_SELECT; sqe->buf_group = BGID; sqe->user_data = 1; ret = io_uring_submit(&ring); if (ret != 1) { fprintf(stderr, "submit: %d\n", ret); goto out_close; } for (i = 0; i < NR_SENDS; i++) { int hdr = sizeof(struct io_uring_recvmsg_out) + sizeof(name); int send_sz = (i < NR_LARGE) ? LARGE_SZ : SMALL_SZ; struct io_uring_recvmsg_out *o; uint8_t *cqe_buf, *pdata; unsigned int plen; int bid; if (write(fds[1], stream + sent_offset, send_sz) != send_sz) { perror("write"); goto out_close; } sent_offset += send_sz; ret = io_uring_wait_cqe(&ring, &cqe); if (ret) { fprintf(stderr, "wait_cqe: %d\n", ret); goto out_close; } if (cqe->res == -EINVAL || cqe->res == -ENOTSUP) { no_recv_mshot = 1; io_uring_cqe_seen(&ring, cqe); ret_val = T_EXIT_SKIP; goto out_close; } if (cqe->res < 0) { fprintf(stderr, "send %d: recvmsg multishot failed: %s (res=%d)\n", i, strerror(-cqe->res), cqe->res); io_uring_cqe_seen(&ring, cqe); goto out_close; } if (!(cqe->flags & IORING_CQE_F_BUFFER)) { fprintf(stderr, "send %d: CQE missing buffer id\n", i); io_uring_cqe_seen(&ring, cqe); goto out_close; } if (cqe->res < hdr) { fprintf(stderr, "send %d: short CQE res=%d (< hdr %d)\n", i, cqe->res, hdr); io_uring_cqe_seen(&ring, cqe); goto out_close; } bid = cqe->flags >> IORING_CQE_BUFFER_SHIFT; if (bid != expected_bids[i]) { fprintf(stderr, "send %d: bid=%d, expected %d\n", i, bid, expected_bids[i]); io_uring_cqe_seen(&ring, cqe); goto out_close; } if (bid != last_bid) { seen_bids++; last_bid = bid; } cqe_buf = (uint8_t *)buf_mem + bid * BUF_SIZE + bid_offset[bid]; o = io_uring_recvmsg_validate(cqe_buf, cqe->res, &msg); if (!o) { fprintf(stderr, "send %d: recvmsg_validate returned NULL (res=%d)\n", i, cqe->res); io_uring_cqe_seen(&ring, cqe); goto out_close; } if (o->controllen != 0) { fprintf(stderr, "send %d: unexpected controllen=%u\n", i, o->controllen); io_uring_cqe_seen(&ring, cqe); goto out_close; } plen = io_uring_recvmsg_payload_length(o, cqe->res, &msg); pdata = io_uring_recvmsg_payload(o, &msg); if (plen != (unsigned int)(cqe->res - hdr)) { fprintf(stderr, "send %d: payload_length=%u, expected %d\n", i, plen, cqe->res - hdr); io_uring_cqe_seen(&ring, cqe); goto out_close; } if (memcmp(pdata, stream + stream_cursor, plen) != 0) { fprintf(stderr, "send %d: payload content mismatch at cursor %zu, plen %u\n", i, stream_cursor, plen); io_uring_cqe_seen(&ring, cqe); goto out_close; } stream_cursor += plen; bid_offset[bid] += cqe->res; io_uring_cqe_seen(&ring, cqe); } if (stream_cursor != expected_payload) { fprintf(stderr, "payload mismatch: got %zu, expected %zu\n", stream_cursor, expected_payload); goto out_close; } /* * Four bids expected: 0 (natural advance), 1 (first retire * target), 2 (second retire target), 3 (third retire target). * Anything else means the retire path didn't behave as planned. */ if (seen_bids != 4) { fprintf(stderr, "expected 4 distinct bids, saw %d\n", seen_bids); goto out_close; } ret_val = T_EXIT_PASS; out_close: close(fds[0]); close(fds[1]); out: io_uring_queue_exit(&ring); if (buf_mem) munmap(buf_mem, NR_BUFS * BUF_SIZE); if (br) { size_t ring_size = NR_BUFS * sizeof(struct io_uring_buf); int page_size = sysconf(_SC_PAGESIZE); ring_size = (ring_size + page_size - 1) & ~(page_size - 1); munmap(br, ring_size); } return ret_val; } int main(int argc, char *argv[]) { if (argc > 1) return T_EXIT_SKIP; return test(); }