Thread (35 messages) 35 messages, 5 authors, 2021-09-15

Re: [RFC PATCH v2 0/9] Add LSM access controls and auditing to io_uring

From: Richard Guy Briggs <hidden>
Date: 2021-08-25 01:36:42
Also in: io-uring, linux-fsdevel, selinux

On 2021-08-24 18:27, Paul Moore wrote:
On Tue, Aug 24, 2021 at 4:57 PM Richard Guy Briggs [off-list ref] wrote:
quoted
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 [off-list ref]
 *
 * 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 [off-list ref]
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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help