On 2021-08-24 18:27, Paul Moore wrote: > On Tue, Aug 24, 2021 at 4:57 PM Richard Guy Briggs <rgb@xxxxxxxxxx> wrote: > > Thanks for the tests. I have a bunch of userspace patches to add to the > > last set I posted and these tests will help exercise them. I also have > > one more kernel patch to post... I'll dive back into that now. I had > > wanted to post them before now but got distracted with AUDIT_TRIM > > breakage. > > If it helps, last week I started working on a little test tool for the > audit-testsuite and selinux-testsuite (see attached). It may not be > final, but I don't expect too many changes to it before I post the > test suite patches; it is definitely usable now. It's inspired by the > previous tests, but it uses a much more test suite friendly fork/exec > model for testing the sharing of io_urings across process boundaries. > > Would you mind sharing your latest userspace patches, if not publicly > I would be okay with privately off-list; I'm putting together the test > suite patches this week and it would be good to make sure I'm using > your latest take on the userspace changes. I intend to publish them but they need squashing and some documentation first. And a run through with io_uring specific tests would be good to catch anything obvious... > Also, what is the kernel patch? Did you find a bug or is this some > new functionality you think might be useful? Both can be important, > but the bug is *really* important; even if you don't have a fix for > that, just a description of the problem would be good. It was a very small patch that I realize I had already talked about and you justified not including sessionid along with auid. That was addressed in a reply tacked on to your v1 patchset just now. > paul moore > /* > * io_uring test tool to exercise LSM/SELinux and audit kernel code paths > * Author: Paul Moore <paul@xxxxxxxxxxxxxx> > * > * Copyright 2021 Microsoft Corporation > * > * At the time this code was written the best, and most current, source of info > * on io_uring seemed to be the liburing sources themselves (link below). The > * code below is based on the lessons learned from looking at the liburing > * code. > * > * -> https://github.com/axboe/liburing > * > * The liburing LICENSE file contains the following: > * > * Copyright 2020 Jens Axboe > * > * Permission is hereby granted, free of charge, to any person obtaining a copy > * of this software and associated documentation files (the "Software"), to > * deal in the Software without restriction, including without limitation the > * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or > * sell copies of the Software, and to permit persons to whom the Software is > * furnished to do so, subject to the following conditions: > * > * The above copyright notice and this permission notice shall be included in > * all copies or substantial portions of the Software. > * > * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE > * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER > * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING > * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER > * DEALINGS IN THE SOFTWARE. > * > */ > > /* > * BUILDING: > * > * gcc -o <binary> -g -O0 -luring -lrt <source> > * > * RUNNING: > * > * The program can be run using the following command lines: > * > * % prog sqpoll > * ... this invocation runs the io_uring SQPOLL test. > * > * % prog t1 > * ... this invocation runs the parent/child io_uring sharing test. > * > * % prog t1 <domain> > * ... this invocation runs the parent/child io_uring sharing test with the > * child process run in the specified SELinux domain. > * > */ > > #include <stdlib.h> > #include <stdio.h> > #include <errno.h> > #include <string.h> > #include <fcntl.h> > #include <unistd.h> > #include <sys/mman.h> > #include <sys/stat.h> > #include <sys/wait.h> > > #include <liburing.h> > > struct urt_config { > struct io_uring ring; > struct io_uring_params ring_params; > int ring_creds; > }; > > #define URING_ENTRIES 8 > #define URING_SHM_NAME "/iouring_test_4" > > int selinux_state = -1; > #define SELINUX_CTX_MAX 512 > char selinux_ctx[SELINUX_CTX_MAX] = "\0"; > > /** > * Display an error message and exit > * @param msg the error message > * > * Output @msg to stderr and exit with errno as the exit value. > */ > void fatal(const char *msg) > { > const char *str = (msg ? msg : "unknown"); > > if (!errno) { > errno = 1; > fprintf(stderr, "%s: unknown error\n", msg); > } else > perror(str); > > if (errno < 0) > exit(-errno); > exit(errno); > } > > /** > * Determine if SELinux is enabled and set the internal state > * > * Attempt to read from /proc/self/attr/current and determine if SELinux is > * enabled, store the current context/domain in @selinux_ctx if SELinux is > * enabled. We avoid using the libselinux API in order to increase portability > * and make it easier for other LSMs to adopt this test. > */ > int selinux_enabled(void) > { > int fd = -1; > ssize_t ctx_len; > char ctx[SELINUX_CTX_MAX]; > > if (selinux_state >= 0) > return selinux_state; > > /* attempt to get the current context */ > fd = open("/proc/self/attr/current", O_RDONLY); > if (fd < 0) > goto err; > ctx_len = read(fd, ctx, SELINUX_CTX_MAX - 1); > if (ctx_len <= 0) > goto err; > close(fd); > > /* save the current context */ > ctx[ctx_len] = '\0'; > strcpy(selinux_ctx, ctx); > > selinux_state = 1; > return selinux_state; > > err: > if (fd >= 0) > close(fd); > > selinux_state = 0; > return selinux_state; > } > > /** > * Return the current SELinux domain or "DISABLED" if SELinux is not enabled > * > * The returned string should not be free()'d. > */ > const char *selinux_current(void) > { > int rc; > > rc = selinux_enabled(); > if (!rc) > return "DISABLED"; > > return selinux_ctx; > } > > /** > * Set the SELinux domain for the next exec()'d process > * @param ctx the SELinux domain > * > * This is similar to the setexeccon() libselinux API but we do it manually to > * help increase portability and make it easier for other LSMs to adopt this > * test. > */ > int selinux_exec(const char *ctx) > { > int fd = -1; > ssize_t len; > > if (!ctx) > return -EINVAL; > > fd = open("/proc/self/attr/exec", O_WRONLY); > if (fd < 0) > return -errno; > len = write(fd, ctx, strlen(ctx) + 1); > close(fd); > > return len; > } > > /** > * Setup the io_uring > * @param ring the io_uring pointer > * @param params the io_uring parameters > * @param creds pointer to the current process' registered io_uring personality > * > * Create a new io_uring using @params and return it in @ring with the > * registered personality returned in @creds. Returns 0 on success, negative > * values on failure. > */ > int uring_setup(struct io_uring *ring, > struct io_uring_params *params, int *creds) > { > int rc; > > /* call into liburing to do the setup heavy lifting */ > rc = io_uring_queue_init_params(URING_ENTRIES, ring, params); > if (rc < 0) > fatal("io_uring_queue_init_params"); > > /* register our creds/personality */ > rc = io_uring_register_personality(ring); > if (rc < 0) > fatal("io_uring_register_personality()"); > *creds = rc; > rc = 0; > > printf(">>> io_uring created; fd = %d, personality = %d\n", > ring->ring_fd, *creds); > > return rc; > } > > /** > * Import an existing io_uring based on the given file descriptor > * @param fd the io_uring's file descriptor > * @param ring the io_uring pointer > * @param params the io_uring parameters > * > * This function takes an io_uring file descriptor in @fd as well as the > * io_uring parameters in @params and creates a valid io_uring in @ring. > * Returns 0 on success, negative values on failure. > */ > int uring_import(int fd, struct io_uring *ring, struct io_uring_params *params) > { > int rc; > > memset(ring, 0, sizeof(*ring)); > ring->flags = params->flags; > ring->features = params->features; > ring->ring_fd = fd; > > ring->sq.ring_sz = params->sq_off.array + > params->sq_entries * sizeof(unsigned); > ring->cq.ring_sz = params->cq_off.cqes + > params->cq_entries * sizeof(struct io_uring_cqe); > > ring->sq.ring_ptr = mmap(NULL, ring->sq.ring_sz, PROT_READ | PROT_WRITE, > MAP_SHARED | MAP_POPULATE, fd, > IORING_OFF_SQ_RING); > if (ring->sq.ring_ptr == MAP_FAILED) > fatal("import mmap(ring)"); > > ring->cq.ring_ptr = mmap(0, ring->cq.ring_sz, PROT_READ | PROT_WRITE, > MAP_SHARED | MAP_POPULATE, > fd, IORING_OFF_CQ_RING); > if (ring->cq.ring_ptr == MAP_FAILED) { > ring->cq.ring_ptr = NULL; > goto err; > } > > ring->sq.khead = ring->sq.ring_ptr + params->sq_off.head; > ring->sq.ktail = ring->sq.ring_ptr + params->sq_off.tail; > ring->sq.kring_mask = ring->sq.ring_ptr + params->sq_off.ring_mask; > ring->sq.kring_entries = ring->sq.ring_ptr + > params->sq_off.ring_entries; > ring->sq.kflags = ring->sq.ring_ptr + params->sq_off.flags; > ring->sq.kdropped = ring->sq.ring_ptr + params->sq_off.dropped; > ring->sq.array = ring->sq.ring_ptr + params->sq_off.array; > > ring->sq.sqes = mmap(NULL, > params->sq_entries * sizeof(struct io_uring_sqe), > PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, > fd, IORING_OFF_SQES); > if (ring->sq.sqes == MAP_FAILED) > goto err; > > ring->cq.khead = ring->cq.ring_ptr + params->cq_off.head; > ring->cq.ktail = ring->cq.ring_ptr + params->cq_off.tail; > ring->cq.kring_mask = ring->cq.ring_ptr + params->cq_off.ring_mask; > ring->cq.kring_entries = ring->cq.ring_ptr + > params->cq_off.ring_entries; > ring->cq.koverflow = ring->cq.ring_ptr + params->cq_off.overflow; > ring->cq.cqes = ring->cq.ring_ptr + params->cq_off.cqes; > if (params->cq_off.flags) > ring->cq.kflags = ring->cq.ring_ptr + params->cq_off.flags; > > return 0; > > err: > if (ring->sq.ring_ptr) > munmap(ring->sq.ring_ptr, ring->sq.ring_sz); > if (ring->cq.ring_ptr); > munmap(ring->cq.ring_ptr, ring->cq.ring_sz); > fatal("import mmap"); > } > > void uring_shutdown(struct io_uring *ring) > { > if (!ring) > return; > io_uring_queue_exit(ring); > } > > /** > * An io_uring test > * @param ring the io_uring pointer > * @param personality the registered personality to use or 0 > * @param path the file path to use for the test > * > * This function executes an io_uring test, see the function body for more > * details. Returns 0 on success, negative values on failure. > */ > int uring_op_a(struct io_uring *ring, int personality, const char *path) > { > > #define __OP_A_BSIZE 512 > #define __OP_A_STR "Lorem ipsum dolor sit amet.\n" > > int rc; > int fds[1]; > char buf1[__OP_A_BSIZE]; > char buf2[__OP_A_BSIZE]; > struct io_uring_sqe *sqe; > struct io_uring_cqe *cqe; > int str_sz = strlen(__OP_A_STR); > > memset(buf1, 0, __OP_A_BSIZE); > memset(buf2, 0, __OP_A_BSIZE); > strncpy(buf1, __OP_A_STR, str_sz); > > if (personality > 0) > printf(">>> io_uring ops using personality = %d\n", > personality); > > /* > * open > */ > > sqe = io_uring_get_sqe(ring); > if (!sqe) > fatal("io_uring_get_sqe(open)"); > io_uring_prep_openat(sqe, AT_FDCWD, path, > O_RDWR | O_TRUNC | O_CREAT, 0644); > if (personality > 0) > sqe->personality = personality; > > rc = io_uring_submit(ring); > if (rc < 0) > fatal("io_uring_submit(open)"); > > rc = io_uring_wait_cqe(ring, &cqe); > fds[0] = cqe->res; > if (rc < 0) > fatal("io_uring_wait_cqe(open)"); > if (fds[0] < 0) > fatal("uring_open"); > io_uring_cqe_seen(ring, cqe); > > rc = io_uring_register_files(ring, fds, 1); > if(rc) > fatal("io_uring_register_files"); > > printf(">>> io_uring open(): OK\n"); > > /* > * write > */ > > sqe = io_uring_get_sqe(ring); > if (!sqe) > fatal("io_uring_get_sqe(write1)"); > io_uring_prep_write(sqe, 0, buf1, str_sz, 0); > io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE); > if (personality > 0) > sqe->personality = personality; > > rc = io_uring_submit(ring); > if (rc < 0) > fatal("io_uring_submit(write)"); > > rc = io_uring_wait_cqe(ring, &cqe); > if (rc < 0) > fatal("io_uring_wait_cqe(write)"); > if (cqe->res < 0) > fatal("uring_write"); > if (cqe->res != str_sz) > fatal("uring_write(length)"); > io_uring_cqe_seen(ring, cqe); > > printf(">>> io_uring write(): OK\n"); > > /* > * read > */ > > sqe = io_uring_get_sqe(ring); > if (!sqe) > fatal("io_uring_get_sqe(read1)"); > io_uring_prep_read(sqe, 0, buf2,__OP_A_BSIZE, 0); > io_uring_sqe_set_flags(sqe, IOSQE_FIXED_FILE); > if (personality > 0) > sqe->personality = personality; > > rc = io_uring_submit(ring); > if (rc < 0) > fatal("io_uring_submit(read)"); > > rc = io_uring_wait_cqe(ring, &cqe); > if (rc < 0) > fatal("io_uring_wait_cqe(read)"); > if (cqe->res < 0) > fatal("uring_read"); > if (cqe->res != str_sz) > fatal("uring_read(length)"); > io_uring_cqe_seen(ring, cqe); > > if (strncmp(buf1, buf2, str_sz)) > fatal("strncmp(buf1,buf2)"); > > printf(">>> io_uring read(): OK\n"); > > /* > * close > */ > > sqe = io_uring_get_sqe(ring); > if (!sqe) > fatal("io_uring_get_sqe(close)"); > io_uring_prep_close(sqe, 0); > if (personality > 0) > sqe->personality = personality; > > rc = io_uring_submit(ring); > if (rc < 0) > fatal("io_uring_submit(close)"); > > rc = io_uring_wait_cqe(ring, &cqe); > if (rc < 0) > fatal("io_uring_wait_cqe(close)"); > if (cqe->res < 0) > fatal("uring_close"); > io_uring_cqe_seen(ring, cqe); > > rc = io_uring_unregister_files(ring); > if (rc < 0) > fatal("io_uring_unregister_files"); > > printf(">>> io_uring close(): OK\n"); > > return 0; > } > > /** > * The main entrypoint to the test program > * @param argc number of command line options > * @param argv the command line options array > */ > int main(int argc, char *argv[]) > { > int rc = 1; > int ring_shm_fd; > struct io_uring ring_storage, *ring; > struct urt_config *cfg_p; > > enum { TST_UNKNOWN, > TST_SQPOLL, > TST_T1_PARENT, TST_T1_CHILD } tst_method; > > /* parse the command line and do some sanity checks */ > tst_method = TST_UNKNOWN; > if (argc >= 2) { > if (!strcmp(argv[1], "sqpoll")) > tst_method = TST_SQPOLL; > else if (!strcmp(argv[1], "t1") || > !strcmp(argv[1], "t1_parent")) > tst_method = TST_T1_PARENT; > else if (!strcmp(argv[1], "t1_child")) > tst_method = TST_T1_CHILD; > } > if (tst_method == TST_UNKNOWN) { > fprintf(stderr, "usage: %s <method> ... \n", argv[0]); > exit(EINVAL); > } > > /* simple header */ > printf(">>> running as PID = %d\n", getpid()); > printf(">>> LSM/SELinux = %s\n", selinux_current()); > > /* > * test setup (if necessary) > */ > if (tst_method == TST_SQPOLL || tst_method == TST_T1_PARENT) { > /* create an io_uring and prepare it for optional sharing */ > int flags; > > /* create a shm segment to hold the io_uring info */ > ring_shm_fd = shm_open(URING_SHM_NAME, O_CREAT | O_RDWR, > S_IRUSR | S_IWUSR); > if (ring_shm_fd < 0) > fatal("shm_open(create)"); > > rc = ftruncate(ring_shm_fd, sizeof(struct urt_config)); > if (rc < 0) > fatal("ftruncate(shm)"); > > cfg_p = mmap(NULL, sizeof(*cfg_p), PROT_READ | PROT_WRITE, > MAP_SHARED, ring_shm_fd, 0); > if (!cfg_p) > fatal("mmap(shm)"); > > /* create the io_uring */ > memset(&cfg_p->ring, 0, sizeof(cfg_p->ring)); > memset(&cfg_p->ring_params, 0, sizeof(cfg_p->ring_params)); > if (tst_method == TST_SQPOLL) > cfg_p->ring_params.flags |= IORING_SETUP_SQPOLL; > rc = uring_setup(&cfg_p->ring, &cfg_p->ring_params, > &cfg_p->ring_creds); > if (rc) > fatal("uring_setup"); > ring = &cfg_p->ring; > > /* explicitly clear FD_CLOEXEC on the io_uring */ > flags = fcntl(cfg_p->ring.ring_fd, F_GETFD, 0); > if (flags < 0) > fatal("fcntl(ring_shm_fd,getfd)"); > flags &= ~FD_CLOEXEC; > rc = fcntl(cfg_p->ring.ring_fd, F_SETFD, flags); > if (rc) > fatal("fcntl(ring_shm_fd,setfd)"); > } else if (tst_method = TST_T1_CHILD) { > /* import a previously created and shared io_uring */ > > /* open the existing shm segment with the io_uring info */ > ring_shm_fd = shm_open(URING_SHM_NAME, O_RDWR, 0); > if (ring_shm_fd < 0) > fatal("shm_open(existing)"); > cfg_p = mmap(NULL, sizeof(*cfg_p), PROT_READ | PROT_WRITE, > MAP_SHARED, ring_shm_fd, 0); > if (!cfg_p) > fatal("mmap(shm)"); > > /* import the io_uring */ > ring = &ring_storage; > rc = uring_import(cfg_p->ring.ring_fd, > ring, &cfg_p->ring_params); > if (rc < 0) > fatal("uring_import"); > } > > /* > * fork/exec a child process (if necessary) > */ > if (tst_method == TST_T1_PARENT) { > pid_t pid; > > /* set the ctx for the next exec */ > if (argc >= 3) { > printf(">>> set LSM/SELinux exec: %s\n", > (selinux_exec(argv[2]) > 0 ? "OK" : "FAILED")); > } > > /* fork/exec */ > pid = fork(); > if (!pid) { > /* start the child */ > rc = execl(argv[0], argv[0], "t1_child", (char *)NULL); > if (rc < 0) > fatal("exec"); > } else { > /* wait for the child to exit */ > int status; > waitpid(pid, &status, 0); > if (WIFEXITED(status)) > rc = WEXITSTATUS(status); > } > } > > /* > * run test(s) > */ > if (tst_method == TST_SQPOLL || tst_method == TST_T1_CHILD) { > rc = uring_op_a(ring, cfg_p->ring_creds, "/tmp/iouring.4.txt"); > if (rc < 0) > fatal("uring_op_a(\"/tmp/iouring.4.txt\")"); > } > > /* > * cleanup > */ > if (tst_method == TST_SQPOLL || tst_method == TST_T1_PARENT) { > printf(">>> shutdown\n"); > uring_shutdown(&cfg_p->ring); > shm_unlink(URING_SHM_NAME); > } else if (tst_method == TST_T1_CHILD) { > shm_unlink(URING_SHM_NAME); > } > > return rc; > } - RGB -- Richard Guy Briggs <rgb@xxxxxxxxxx> Sr. S/W Engineer, Kernel Security, Base Operating Systems Remote, Ottawa, Red Hat Canada IRC: rgb, SunRaycer Voice: +1.647.777.2635, Internal: (81) 32635