blob: 7070dbd572f536c3521f06259b25750a658041aa [file]
// 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;
}