| /* SPDX-License-Identifier: MIT */ |
| /* |
| * Description: test SQE_MIXED physical SQE boundary validation with sq_array |
| * |
| * Verify that 128-byte operations are correctly rejected when sq_array |
| * remaps them to the last physical SQE slot, preventing a 64-byte OOB |
| * read past the SQE array. |
| */ |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "liburing.h" |
| #include "helpers.h" |
| #include "test.h" |
| |
| #define NENTRIES 4 |
| |
| /* |
| * Positive test: NOP128 at a valid physical position should succeed. |
| */ |
| static int test_valid_position(void) |
| { |
| struct io_uring ring; |
| struct io_uring_params p = { .flags = IORING_SETUP_SQE_MIXED }; |
| struct io_uring_cqe *cqe; |
| struct io_uring_sqe *sqe; |
| int ret; |
| |
| ret = t_io_uring_init_sqarray(NENTRIES, &ring, &p); |
| if (ret) { |
| if (ret == -EINVAL) |
| return T_EXIT_SKIP; |
| fprintf(stderr, "ring init: %d\n", ret); |
| return T_EXIT_FAIL; |
| } |
| |
| sqe = io_uring_get_sqe(&ring); |
| io_uring_prep_nop(sqe); |
| sqe->user_data = 1; |
| |
| sqe = io_uring_get_sqe128(&ring); |
| if (!sqe) { |
| fprintf(stderr, "get_sqe128 failed\n"); |
| goto fail; |
| } |
| io_uring_prep_nop128(sqe); |
| sqe->user_data = 2; |
| |
| ret = io_uring_submit(&ring); |
| if (ret < 0) { |
| fprintf(stderr, "submit: %d\n", ret); |
| goto fail; |
| } |
| |
| ret = io_uring_wait_cqe(&ring, &cqe); |
| if (ret) { |
| fprintf(stderr, "wait_cqe: %d\n", ret); |
| goto fail; |
| } |
| io_uring_cqe_seen(&ring, cqe); |
| |
| ret = io_uring_wait_cqe(&ring, &cqe); |
| if (ret) { |
| fprintf(stderr, "wait_cqe: %d\n", ret); |
| goto fail; |
| } |
| if (cqe->user_data == 2 && cqe->res != 0) { |
| fprintf(stderr, "NOP128 at valid position failed: %d\n", |
| cqe->res); |
| io_uring_cqe_seen(&ring, cqe); |
| goto fail; |
| } |
| io_uring_cqe_seen(&ring, cqe); |
| |
| io_uring_queue_exit(&ring); |
| return T_EXIT_PASS; |
| fail: |
| io_uring_queue_exit(&ring); |
| return T_EXIT_FAIL; |
| } |
| |
| /* |
| * Negative test: NOP128 at the last physical SQE slot via sq_array remap |
| * must be rejected. Without the kernel fix, this triggers a 64-byte OOB |
| * read in io_uring_cmd_sqe_copy(). |
| */ |
| static int test_oob_boundary(void) |
| { |
| struct io_uring ring; |
| struct io_uring_params p = { .flags = IORING_SETUP_SQE_MIXED }; |
| struct io_uring_cqe *cqe; |
| struct io_uring_sqe *sqe; |
| unsigned mask; |
| int ret, i, found; |
| |
| ret = t_io_uring_init_sqarray(NENTRIES, &ring, &p); |
| if (ret) { |
| if (ret == -EINVAL) |
| return T_EXIT_SKIP; |
| fprintf(stderr, "ring init: %d\n", ret); |
| return T_EXIT_FAIL; |
| } |
| |
| mask = *ring.sq.kring_entries - 1; |
| |
| /* Advance internal tail: NOP (1) + NOP128 (2) = 3 slots */ |
| sqe = io_uring_get_sqe(&ring); |
| io_uring_prep_nop(sqe); |
| sqe->user_data = 1; |
| |
| sqe = io_uring_get_sqe128(&ring); |
| if (!sqe) { |
| fprintf(stderr, "get_sqe128 failed\n"); |
| goto fail; |
| } |
| |
| /* |
| * Override: remap logical position 1 to last physical slot. |
| * Prep NOP128 there instead of the position get_sqe128 returned. |
| */ |
| ring.sq.array[1] = mask; |
| memset(&ring.sq.sqes[mask], 0, sizeof(struct io_uring_sqe)); |
| io_uring_prep_nop128(&ring.sq.sqes[mask]); |
| ring.sq.sqes[mask].user_data = 2; |
| |
| ret = io_uring_submit(&ring); |
| if (ret < 0) { |
| fprintf(stderr, "submit: %d\n", ret); |
| goto fail; |
| } |
| |
| found = 0; |
| for (i = 0; i < 2; i++) { |
| ret = io_uring_wait_cqe(&ring, &cqe); |
| if (ret) |
| break; |
| if (cqe->user_data == 2) { |
| if (cqe->res != -EINVAL) { |
| fprintf(stderr, |
| "NOP128 at last slot: expected -EINVAL, got %d\n", |
| cqe->res); |
| io_uring_cqe_seen(&ring, cqe); |
| goto fail; |
| } |
| found = 1; |
| } |
| io_uring_cqe_seen(&ring, cqe); |
| } |
| |
| if (!found) { |
| fprintf(stderr, "no CQE for NOP128 boundary test\n"); |
| goto fail; |
| } |
| |
| io_uring_queue_exit(&ring); |
| return T_EXIT_PASS; |
| fail: |
| io_uring_queue_exit(&ring); |
| return T_EXIT_FAIL; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int ret; |
| |
| if (argc > 1) |
| return T_EXIT_SKIP; |
| |
| ret = test_valid_position(); |
| if (ret == T_EXIT_SKIP) |
| return T_EXIT_SKIP; |
| if (ret) { |
| fprintf(stderr, "test_valid_position failed\n"); |
| return T_EXIT_FAIL; |
| } |
| |
| ret = test_oob_boundary(); |
| if (ret) { |
| fprintf(stderr, "test_oob_boundary failed\n"); |
| return ret; |
| } |
| |
| return T_EXIT_PASS; |
| } |