/* SPDX-License-Identifier: MIT */ /* * Description: test installing a direct descriptor into the regular * file table * */ #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" static int no_fd_install; /* test that O_CLOEXEC is accepted, and others are not */ static int test_flags(struct io_uring *ring, int async) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret, fds[2], fd; if (pipe(fds) < 0) { perror("pipe"); return T_EXIT_FAIL; } ret = io_uring_register_files(ring, &fds[0], 1); if (ret) { fprintf(stderr, "failed register files %d\n", ret); return T_EXIT_FAIL; } /* check that setting an invalid flag fails */ sqe = io_uring_get_sqe(ring); io_uring_prep_fixed_fd_install(sqe, 0, 1U << 17); io_uring_submit(ring); ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res != -EINVAL) { fprintf(stderr, "unexpected cqe res %d\n", cqe->res); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); /* check that IORING_FIXED_FD_NO_CLOEXEC is accepted */ sqe = io_uring_get_sqe(ring); io_uring_prep_fixed_fd_install(sqe, 0, IORING_FIXED_FD_NO_CLOEXEC); if (async) sqe->flags |= IOSQE_ASYNC; io_uring_submit(ring); ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res < 0) { fprintf(stderr, "unexpected cqe res %d\n", cqe->res); return T_EXIT_FAIL; } fd = cqe->res; io_uring_cqe_seen(ring, cqe); close(fds[0]); close(fds[1]); close(fd); io_uring_unregister_files(ring); return T_EXIT_PASS; } static int test_linked(struct io_uring *ring) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret, fds[2], fd, i; if (pipe(fds) < 0) { perror("pipe"); return T_EXIT_FAIL; } ret = io_uring_register_files(ring, &fds[0], 1); if (ret) { fprintf(stderr, "failed register files %d\n", ret); return T_EXIT_FAIL; } sqe = io_uring_get_sqe(ring); io_uring_prep_nop(sqe); sqe->flags |= IOSQE_IO_LINK; sqe->user_data = 1; sqe = io_uring_get_sqe(ring); io_uring_prep_fixed_fd_install(sqe, 0, 0); sqe->user_data = 2; ret = io_uring_submit(ring); if (ret != 2) { fprintf(stderr, "submit: %d\n", ret); return T_EXIT_FAIL; } fd = -1; for (i = 0; i < 2; i++) { ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res < 0) { fprintf(stderr, "unexpected cqe res %d\n", cqe->res); return T_EXIT_FAIL; } if (cqe->user_data == 2) fd = cqe->res; io_uring_cqe_seen(ring, cqe); } close(fds[0]); close(fds[1]); if (fd != -1) close(fd); io_uring_unregister_files(ring); return T_EXIT_PASS; } /* test not setting IOSQE_FIXED_FILE */ static int test_not_fixed(struct io_uring *ring) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret, fds[2]; if (pipe(fds) < 0) { perror("pipe"); return T_EXIT_FAIL; } ret = io_uring_register_files(ring, &fds[0], 1); if (ret) { fprintf(stderr, "failed register files %d\n", ret); return T_EXIT_FAIL; } sqe = io_uring_get_sqe(ring); io_uring_prep_fixed_fd_install(sqe, 0, 0); sqe->flags &= ~IOSQE_FIXED_FILE; io_uring_submit(ring); ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res != -EBADF) { fprintf(stderr, "unexpected cqe res %d\n", cqe->res); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); close(fds[0]); close(fds[1]); io_uring_unregister_files(ring); return T_EXIT_PASS; } /* test invalid direct descriptor indexes */ static int test_bad_fd(struct io_uring *ring, int some_fd) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret; sqe = io_uring_get_sqe(ring); io_uring_prep_fixed_fd_install(sqe, some_fd, 0); io_uring_submit(ring); ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res != -EBADF) { fprintf(stderr, "unexpected cqe res %d\n", cqe->res); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); return T_EXIT_PASS; } /* test basic functionality of shifting a direct descriptor to a normal file */ static int test_working(struct io_uring *ring) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int ret, fds[2]; char buf[32]; if (pipe(fds) < 0) { perror("pipe"); return T_EXIT_FAIL; } /* register read side */ ret = io_uring_register_files(ring, &fds[0], 1); if (ret) { fprintf(stderr, "failed register files %d\n", ret); return T_EXIT_FAIL; } /* close normal descriptor */ close(fds[0]); /* normal read should fail */ ret = read(fds[0], buf, 1); if (ret != -1) { fprintf(stderr, "unexpected read ret %d\n", ret); return T_EXIT_FAIL; } if (errno != EBADF) { fprintf(stderr, "unexpected read failure %d\n", errno); return T_EXIT_FAIL; } /* verify we can read the data */ sqe = io_uring_get_sqe(ring); io_uring_prep_read(sqe, 0, buf, sizeof(buf), 0); sqe->flags |= IOSQE_FIXED_FILE; io_uring_submit(ring); /* put some data in the pipe */ ret = write(fds[1], "Hello", 5); if (ret < 0) { perror("write"); return T_EXIT_FAIL; } else if (ret != 5) { fprintf(stderr, "short write %d\n", ret); return T_EXIT_FAIL; } ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res != 5) { fprintf(stderr, "weird pipe read ret %d\n", cqe->res); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); /* fixed pipe read worked, now re-install as a regular fd */ sqe = io_uring_get_sqe(ring); io_uring_prep_fixed_fd_install(sqe, 0, 0); io_uring_submit(ring); ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res == -EINVAL) { no_fd_install = 1; return T_EXIT_SKIP; } if (cqe->res < 0) { fprintf(stderr, "failed install fd: %d\n", cqe->res); return T_EXIT_FAIL; } /* stash new pipe read side fd in old spot */ fds[0] = cqe->res; io_uring_cqe_seen(ring, cqe); ret = write(fds[1], "Hello", 5); if (ret < 0) { perror("write"); return T_EXIT_FAIL; } else if (ret != 5) { fprintf(stderr, "short write %d\n", ret); return T_EXIT_FAIL; } /* normal pipe read should now work with new fd */ ret = read(fds[0], buf, sizeof(buf)); if (ret != 5) { fprintf(stderr, "unexpected read ret %d\n", ret); return T_EXIT_FAIL; } /* close fixed file */ sqe = io_uring_get_sqe(ring); io_uring_prep_close_direct(sqe, 0); io_uring_submit(ring); ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res) { fprintf(stderr, "close fixed fd %d\n", cqe->res); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); ret = write(fds[1], "Hello", 5); if (ret < 0) { perror("write"); return T_EXIT_FAIL; } else if (ret != 5) { fprintf(stderr, "short write %d\n", ret); return T_EXIT_FAIL; } /* normal pipe read should still work with new fd */ ret = read(fds[0], buf, sizeof(buf)); if (ret != 5) { fprintf(stderr, "unexpected read ret %d\n", ret); return T_EXIT_FAIL; } /* fixed fd pipe read should now fail */ sqe = io_uring_get_sqe(ring); io_uring_prep_read(sqe, 0, buf, sizeof(buf), 0); sqe->flags = IOSQE_FIXED_FILE; io_uring_submit(ring); /* put some data in the pipe */ ret = write(fds[1], "Hello", 5); if (ret < 0) { perror("write"); return T_EXIT_FAIL; } else if (ret != 5) { fprintf(stderr, "short write %d\n", ret); return T_EXIT_FAIL; } ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res != -EBADF) { fprintf(stderr, "weird pipe read ret %d\n", cqe->res); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); close(fds[0]); close(fds[1]); io_uring_unregister_files(ring); return T_EXIT_PASS; } static int test_creds(struct io_uring *ring, int async) { struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; int cred_id, ret, fds[2]; if (pipe(fds) < 0) { perror("pipe"); return T_EXIT_FAIL; } ret = io_uring_register_files(ring, &fds[0], 1); if (ret) { fprintf(stderr, "failed register files %d\n", ret); return T_EXIT_FAIL; } cred_id = io_uring_register_personality(ring); if (cred_id < 0) { fprintf(stderr, "Failed registering creds: %d\n", cred_id); return T_EXIT_FAIL; } /* check that asking for creds fails */ sqe = io_uring_get_sqe(ring); io_uring_prep_fixed_fd_install(sqe, 0, 0); if (async) sqe->flags |= IOSQE_ASYNC; sqe->personality = cred_id; io_uring_submit(ring); ret = io_uring_wait_cqe(ring, &cqe); if (ret) { fprintf(stderr, "wait cqe %d\n", ret); return T_EXIT_FAIL; } if (cqe->res > 0) { fprintf(stderr, "install succeeded with creds\n"); return T_EXIT_FAIL; } if (cqe->res != -EPERM) { fprintf(stderr, "unexpected cqe res %d\n", cqe->res); return T_EXIT_FAIL; } io_uring_cqe_seen(ring, cqe); close(fds[0]); close(fds[1]); io_uring_unregister_files(ring); io_uring_unregister_personality(ring, cred_id); return T_EXIT_PASS; } int main(int argc, char *argv[]) { struct io_uring ring; int ret; if (argc > 1) return T_EXIT_SKIP; ret = io_uring_queue_init(4, &ring, 0); if (ret) { fprintf(stderr, "ring setup failed: %d\n", ret); return T_EXIT_FAIL; } ret = test_working(&ring); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_working failed\n"); return ret; } if (no_fd_install) return T_EXIT_SKIP; ret = test_bad_fd(&ring, 0); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_bad_fd 0 failed\n"); return ret; } ret = test_bad_fd(&ring, 500); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_bad_fd 500 failed\n"); return ret; } ret = test_not_fixed(&ring); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_not_fixed failed\n"); return ret; } ret = test_flags(&ring, 0); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_flags 0 failed\n"); return ret; } ret = test_flags(&ring, 1); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_flags 1 failed\n"); return ret; } ret = test_creds(&ring, 0); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_creds 0 failed\n"); return ret; } ret = test_creds(&ring, 1); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_creds 1 failed\n"); return ret; } ret = test_linked(&ring); if (ret != T_EXIT_PASS) { if (ret == T_EXIT_FAIL) fprintf(stderr, "test_linked failed\n"); return ret; } return T_EXIT_PASS; }