| // SPDX-License-Identifier: MIT |
| /* |
| * Test that auto-allocated fixed file slots stay within the configured |
| * alloc range, even after explicit operations on slots outside the range |
| * corrupt alloc_hint. |
| * |
| * Bug: io_file_bitmap_clear() and io_file_bitmap_set() unconditionally |
| * update alloc_hint to the bit position. If a file is installed/removed |
| * at a slot outside the configured alloc range, alloc_hint can point |
| * outside the range, causing subsequent auto-allocations to escape it. |
| */ |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| #include "helpers.h" |
| #include "liburing.h" |
| |
| static int no_sparse; |
| |
| /* |
| * Auto-alloc a file into the fixed table using IORING_FILE_INDEX_ALLOC. |
| * On success, the kernel writes back the allocated slot index into *fd |
| * (via put_user). Returns cqe->res (count on success, negative on error). |
| */ |
| static int file_update_alloc(struct io_uring *ring, int *fd) |
| { |
| struct io_uring_sqe *sqe; |
| struct io_uring_cqe *cqe; |
| int ret; |
| |
| sqe = io_uring_get_sqe(ring); |
| io_uring_prep_files_update(sqe, fd, 1, IORING_FILE_INDEX_ALLOC); |
| |
| io_uring_submit(ring); |
| |
| ret = io_uring_wait_cqe(ring, &cqe); |
| if (ret < 0) { |
| fprintf(stderr, "wait_cqe: %d\n", ret); |
| return -1; |
| } |
| ret = cqe->res; |
| io_uring_cqe_seen(ring, cqe); |
| return ret; |
| } |
| |
| /* |
| * Install a file at a slot below the alloc range, then remove it. |
| * This corrupts alloc_hint to point below the range. Verify that |
| * subsequent auto-alloc still returns a slot within the range. |
| */ |
| static int test_hint_below_range(void) |
| { |
| struct io_uring ring; |
| int pipe_fds[2]; |
| int roff = 10, rlen = 10; |
| int ret, fd; |
| |
| if (pipe(pipe_fds)) { |
| fprintf(stderr, "pipe\n"); |
| return 1; |
| } |
| |
| ret = io_uring_queue_init(8, &ring, 0); |
| if (ret) { |
| fprintf(stderr, "queue_init: %d\n", ret); |
| return 1; |
| } |
| |
| ret = io_uring_register_files_sparse(&ring, 20); |
| if (ret) { |
| if (ret == -EINVAL) { |
| no_sparse = 1; |
| goto err; |
| } |
| fprintf(stderr, "register_files_sparse: %d\n", ret); |
| goto err; |
| } |
| |
| ret = io_uring_register_file_alloc_range(&ring, roff, rlen); |
| if (ret) { |
| fprintf(stderr, "register_file_alloc_range: %d\n", ret); |
| goto err; |
| } |
| |
| /* Install a file at slot 2, outside the alloc range [10, 20) */ |
| fd = pipe_fds[0]; |
| ret = io_uring_register_files_update(&ring, 2, &fd, 1); |
| if (ret != 1) { |
| fprintf(stderr, "install at slot 2: %d\n", ret); |
| goto err; |
| } |
| |
| /* Remove it — corrupts alloc_hint to point at slot 2 */ |
| fd = -1; |
| ret = io_uring_register_files_update(&ring, 2, &fd, 1); |
| if (ret != 1) { |
| fprintf(stderr, "remove at slot 2: %d\n", ret); |
| goto err; |
| } |
| |
| /* Auto-alloc should stay within [10, 20), not land at slot 2 */ |
| fd = pipe_fds[1]; |
| ret = file_update_alloc(&ring, &fd); |
| if (ret != 1) { |
| fprintf(stderr, "file_update_alloc: %d\n", ret); |
| goto err; |
| } |
| |
| if (fd < roff || fd >= roff + rlen) { |
| fprintf(stderr, "alloc returned slot %d, expected [%d, %d)\n", |
| fd, roff, roff + rlen); |
| goto err; |
| } |
| |
| close(pipe_fds[0]); |
| close(pipe_fds[1]); |
| io_uring_queue_exit(&ring); |
| return T_EXIT_PASS; |
| err: |
| close(pipe_fds[0]); |
| close(pipe_fds[1]); |
| io_uring_queue_exit(&ring); |
| return no_sparse ? T_EXIT_SKIP : T_EXIT_FAIL; |
| } |
| |
| /* |
| * Install a file at a slot above the alloc range end. The bitmap_set |
| * pushes alloc_hint above file_alloc_end. Verify auto-alloc still |
| * stays within range. |
| */ |
| static int test_hint_above_range(void) |
| { |
| struct io_uring ring; |
| int pipe_fds[2]; |
| int roff = 0, rlen = 10; |
| int ret, fd; |
| |
| if (pipe(pipe_fds)) { |
| fprintf(stderr, "pipe\n"); |
| return 1; |
| } |
| |
| ret = io_uring_queue_init(8, &ring, 0); |
| if (ret) { |
| fprintf(stderr, "queue_init: %d\n", ret); |
| return 1; |
| } |
| |
| ret = io_uring_register_files_sparse(&ring, 20); |
| if (ret) { |
| fprintf(stderr, "register_files_sparse: %d\n", ret); |
| goto err; |
| } |
| |
| ret = io_uring_register_file_alloc_range(&ring, roff, rlen); |
| if (ret) { |
| fprintf(stderr, "register_file_alloc_range: %d\n", ret); |
| goto err; |
| } |
| |
| /* Install at slot 15, above range [0, 10) — alloc_hint = 16 */ |
| fd = pipe_fds[0]; |
| ret = io_uring_register_files_update(&ring, 15, &fd, 1); |
| if (ret != 1) { |
| fprintf(stderr, "install at slot 15: %d\n", ret); |
| goto err; |
| } |
| |
| /* Auto-alloc should stay within [0, 10), not search [0, 16) */ |
| fd = pipe_fds[1]; |
| ret = file_update_alloc(&ring, &fd); |
| if (ret != 1) { |
| fprintf(stderr, "file_update_alloc: %d\n", ret); |
| goto err; |
| } |
| |
| if (fd < roff || fd >= roff + rlen) { |
| fprintf(stderr, "alloc returned slot %d, expected [%d, %d)\n", |
| fd, roff, roff + rlen); |
| goto err; |
| } |
| |
| close(pipe_fds[0]); |
| close(pipe_fds[1]); |
| io_uring_queue_exit(&ring); |
| return 0; |
| err: |
| close(pipe_fds[0]); |
| close(pipe_fds[1]); |
| io_uring_queue_exit(&ring); |
| return 1; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int ret; |
| |
| if (argc > 1) |
| return T_EXIT_SKIP; |
| |
| ret = test_hint_below_range(); |
| if (ret == T_EXIT_SKIP) { |
| return T_EXIT_SKIP; |
| } else if (ret) { |
| fprintf(stderr, "test_hint_below_range failed\n"); |
| return T_EXIT_FAIL; |
| } |
| if (no_sparse) |
| return T_EXIT_SKIP; |
| |
| ret = test_hint_above_range(); |
| if (ret) { |
| fprintf(stderr, "test_hint_above_range failed\n"); |
| return T_EXIT_FAIL; |
| } |
| |
| return T_EXIT_PASS; |
| } |