⬢ DragonFlyBSD Kernel Audit
DF-4147 / fixtest.c
← back to finding ↓ download raw
/*
 * DF-4147 fix functional test: the cross-process fallback path must now
 * actually copy between the SERVER's user memory and the CLIENT's user
 * memory (the old raw-copyin/copyout implementation wrote into the
 * server's own address space at the client's VA, or EFAULTed).
 *
 * flow: client read(8192) -> window too big for the fast path -> server
 * CUSE_IOCTL_WRITE_DATA{peer = client buffer VA} -> must land in the
 * CLIENT's buffer -> SYNC(15) -> client's read returns 15 bytes.
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioccom.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <time.h>

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define	CUSE_ERR_NONE		0
enum { CUSE_CMD_NONE, CUSE_CMD_OPEN, CUSE_CMD_CLOSE, CUSE_CMD_READ,
       CUSE_CMD_WRITE, CUSE_CMD_IOCTL, CUSE_CMD_POLL, CUSE_CMD_SIGNAL,
       CUSE_CMD_SYNC, CUSE_CMD_MAX };
struct cuse_dev;
struct cuse_data_chunk { uintptr_t local_ptr, peer_ptr; unsigned long length; };
struct cuse_command {
	struct cuse_dev *dev; unsigned long fflags; uintptr_t per_file_handle;
	uintptr_t data_pointer; unsigned long argument, command;
};
struct cuse_create_dev {
	struct cuse_dev *dev; uid_t user_id; gid_t group_id; int permissions;
	char devname[80];
};
#define	CUSE_IOCTL_GET_COMMAND	_IOR('C', 0, struct cuse_command)
#define	CUSE_IOCTL_WRITE_DATA	_IOW('C', 1, struct cuse_data_chunk)
#define	CUSE_IOCTL_SYNC_COMMAND	_IOW('C', 3, int)
#define	CUSE_IOCTL_CREATE_DEV	_IOW('C', 8, struct cuse_create_dev)

int
main(void)
{
	struct cuse_create_dev cd;
	struct cuse_command cmd;
	struct cuse_data_chunk ck;
	char local[32] = "HELLO-FIX-WORKS";
	int fd, status, rv = 15;
	pid_t client;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("[*] fixtest start\n");

	fd = open("/dev/cuse", O_RDWR);
	if (fd < 0) { perror("open /dev/cuse"); return 1; }

	memset(&cd, 0, sizeof(cd));
	snprintf(cd.devname, sizeof(cd.devname), "cf%lx",
	    (unsigned long)time(NULL));
	cd.permissions = 0666;
	if (ioctl(fd, CUSE_IOCTL_CREATE_DEV, &cd) != 0) {
		perror("CREATE_DEV"); return 1; }
	setenv("CUSE_FIXT_DEV", cd.devname, 1);

	client = fork();
	if (client == 0) {
		char *big = malloc(8192);
		int c = -1, i;
		close(fd);			/* drop inherited /dev/cuse */
		setgid(65534); setuid(65534);
#if 0
		/* desync our address space from the server's */
		void *m = mmap(NULL, 64*1024*1024, PROT_READ|PROT_WRITE,
		    MAP_ANON|MAP_PRIVATE, -1, 0);
		(void)m;
#endif
		char path[128];
		snprintf(path, sizeof(path), "/dev/%s", getenv("CUSE_FIXT_DEV"));
		for (i = 0; i < 500; i++) {
			c = open(path, O_RDWR);
			if (c >= 0) break;
			if (i == 499)
				printf("[-] client open %s failed: %s\n",
				    path, strerror(errno));
			usleep(1000);
		}
		printf("[*] client opened fd=%d\n", c);
		if (c < 0) _exit(9);
		memset(big, 0, 8192);
		printf("[*] client entering read(8192) buf=%p\n", big);
		ssize_t r = read(c, big, 8192);
		printf("[*] client read returned %zd\n", r);
		if (r == 15 && memcmp(big, "HELLO-FIX-WORKS", 15) == 0) {
			printf("[+] cross-process fallback copy WORKS "
			    "(client got \"%15.15s\")\n", big);
			_exit(0);
		}
		printf("[-] read=%zd buf=\"%15.15s\" - copy did not reach "
		    "the client\n", r, big);
		_exit(1);
	}

	/* complete the client's OPEN first, then wait for the real READ */
	printf("[*] dev=%s waiting for commands\n", cd.devname);
	for (;;) {
		if (ioctl(fd, CUSE_IOCTL_GET_COMMAND, &cmd) != 0) {
			perror("GET_COMMAND"); return 1;
		}
		printf("[*] got command %lu\n", cmd.command);
		if (cmd.command == CUSE_CMD_READ)
			break;
		rv = CUSE_ERR_NONE;
		ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv);
	}
	rv = 15;
	printf("[+] READ command: data_pointer=%p len=%lu\n",
	    (void *)cmd.data_pointer, cmd.argument);

	/* push data straight to the client's buffer via the fallback path */
	ck.local_ptr = (uintptr_t)local;
	ck.peer_ptr = cmd.data_pointer;
	ck.length = 15;
	if (ioctl(fd, CUSE_IOCTL_WRITE_DATA, &ck) != 0)
		printf("[*] WRITE_DATA returned %d (%s)\n", errno,
		    strerror(errno));
	if (ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv) != 0)
		perror("SYNC");

	/* drain the client's CLOSE so its exit doesn't block */
	{
		struct cuse_command c2;
		int rv2 = CUSE_ERR_NONE;
		if (ioctl(fd, CUSE_IOCTL_GET_COMMAND, &c2) == 0)
			ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv2);
	}

	waitpid(client, &status, 0);
	close(fd);
	printf("[*] client exit status %d\n", WEXITSTATUS(status));
	return (WEXITSTATUS(status) == 0) ? 0 : 1;
}