⬢ DragonFlyBSD Kernel Audit
DF-3162 / revoke_fifo_noop.c
← back to finding ↓ download raw
/*
 * DF-3162 PoC: revoke(2) on a FIFO is a silent no-op on DragonFlyBSD.
 *
 * Root cause: vrevoke() forwards non-VCHR vnodes to fdrevoke(vp, DTYPE_VNODE)
 *            (sys/kern/vfs_subr.c:1450-1452), but fifo file pointers are
 *            created with f_type = DTYPE_FIFO by vop_stdopen()
 *            (sys/kern/vfs_default.c:1172-1175), and fdrevoke's matcher
 *            compares f_type (sys/kern/kern_descrip.c:1999).  The match
 *            never succeeds, so no fp is marked FREVOKED and no descriptor
 *            is closed.
 *
 * The fifo is owned by the invoking user, so revoke(2) passes the
 * ownership/caps check (sys/kern/vfs_syscalls.c:4741-4742) and returns 0.
 *
 * Expected (bug present): revoke() returns 0 and the previously-opened
 * descriptors STILL PASS DATA.
 * Expected (fixed):       descriptors are revoked - write() fails
 *                         (EPIPE/EBADF...), read() does not see data.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int
main(void)
{
	const char *path = "/tmp/df3162_fifo";
	char buf[8];
	ssize_t n;
	int rfd, wfd, unrevoked = 0;

	unlink(path);
	if (mkfifo(path, 0600) < 0) {
		perror("mkfifo");
		exit(1);
	}
	rfd = open(path, O_RDONLY | O_NONBLOCK);
	wfd = open(path, O_WRONLY);
	if (rfd < 0 || wfd < 0) {
		perror("open");
		exit(1);
	}
	printf("fifo opened: rfd=%d wfd=%d (uid=%d)\n", rfd, wfd, getuid());

	if (revoke(path) < 0) {
		perror("revoke");
		exit(1);
	}
	printf("revoke(%s) returned 0 (owner is allowed to revoke)\n", path);
	usleep(200000);	/* give any async revocation machinery a chance */

	if (write(wfd, "X", 1) == 1) {
		n = read(rfd, buf, sizeof(buf));
		if (n == 1 && buf[0] == 'X') {
			printf("BUG-DF-3162: fds still functional after revoke(2) "
			       "(data rfd<->wfd still flows)\n");
			unrevoked = 1;
		} else {
			printf("PARTIAL: write ok, read n=%zd errno=%s\n",
			       n, strerror(errno));
		}
	} else {
		printf("OK: write after revoke failed: %s\n", strerror(errno));
	}

	unlink(path);
	if (unrevoked)
		printf("VERDICT: reproduced\n");
	else
		printf("VERDICT: not_reproduced\n");
	return (unrevoked ? 0 : 1);
}