On 2021-08-24 18:27, Paul Moore wrote:
> On Tue, Aug 24, 2021 at 4:57 PM Richard Guy Briggs <[email protected]> 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 <[email protected]>
>  *
>  * 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 <[email protected]>
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

--
Linux-audit mailing list
[email protected]
https://listman.redhat.com/mailman/listinfo/linux-audit

Reply via email to