DragonFlyBSD Kernel Audit
DF-0145 / df0145_pop.c
← back to finding ↓ download raw
/*
 * DF-0145 - vq_done stub leaks all quota RB-trees on unmount.
 *
 * sys/kern/vfs_quota.c:142-146:
 *   void vq_done(struct mount *mp) { [TODO: remove the rb trees here] }
 *
 * On mount, VFS_ACINIT (vfs_vfsops.c:112) -> vfs_stdac_init -> vq_init sets up
 * the per-mount RB trees and enables accounting.  Accounting population
 * (vfs_stdaccount / cmd_set_limit_uid -> unode_insert/gnode_insert) kmalloc's
 * ac_unode / ac_gnode (M_MOUNT).  On unmount, VFS_ACDONE (vfs_vfsops.c:131) ->
 * vfs_stdac_done -> vq_done is the empty stub, so every node is leaked.
 *
 * This populator drives cmd_set_limit_uid -> unode_insert for N distinct uids
 * on a mounted, quota-enabled path, growing the ac_uroot RB tree.  The
 * accompanying run0145.sh mounts tmpfs there, runs this populator, records
 * M_MOUNT, unmounts (triggering the leak), and records M_MOUNT again.
 *
 * Build:  cc -o df0145_pop df0145_pop.c -lprop
 */
#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 <libprop/proplib.h>

static int
set_limit_uid(const char *path, uid_t uid, uint64_t limit)
{
	prop_dictionary_t dict, args, res = NULL;
	struct plistref pref;
	int error;

	dict = prop_dictionary_create();
	prop_dictionary_set_cstring(dict, "command", "set limit uid");
	args = prop_dictionary_create();
	prop_dictionary_set_uint32(args, "uid", uid);
	prop_dictionary_set_uint64(args, "limit", limit);
	prop_dictionary_set(dict, "arguments", args);
	error = prop_dictionary_send_syscall(dict, &pref);
	if (error == 0)
		error = vquotactl(path, &pref);
	if (error == 0)
		prop_dictionary_recv_syscall(&pref, &res);
	prop_object_release(dict);
	if (res)
		prop_object_release(res);
	return error;
}

int
main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/mnt/df0145";
	int n = argc > 2 ? atoi(argv[2]) : 200;
	int i, ok = 0;

	/* distinct uid chunks (uid >> 5) force distinct unode_insert() calls */
	for (i = 0; i < n; i++) {
		uid_t uid = (uid_t)i * 32;
		if (set_limit_uid(path, uid, 1ULL<<20) == 0)
			ok++;
	}
	printf("populated %d/%d uid chunks on %s (each -> unode_insert kmalloc M_MOUNT)\n",
	    ok, n, path);
	return 0;
}