โฌข DragonFlyBSD Kernel Audit
DF-5704 / df5704_userland.c
โ† back to finding โ†“ download raw
/*
 * DF-5704 โ€” equals-walk stack abandonment in prop_object_equals_with_error
 * (sys/libprop/prop_object.c:1155-1156) violating the _prop_stack drain
 * contract (sys/libprop/prop_stack.h:60-62, prop_stack.c pop/drain).
 *
 * Userland differential against the SAME sys/libprop source the kernel
 * compiles (lib/libprop/Makefile .PATH ${.CURDIR}/../../sys/libprop).
 *
 * Mechanism:
 *   _prop_array_equals() takes BOTH arrays' rwlocks on first iteration
 *   (prop_array.c:219-227) and returns _PROP_OBJECT_EQUALS_RECURSE with
 *   them still held; only pot_equals_finish() (prop_array.c:251-256),
 *   invoked by the finish: drain loop, releases them.  When the walk
 *   hits a nested pair of DIFFERENT types, the early return at
 *   prop_object.c:1155-1156 skips the drain: every ancestor's shared
 *   lock is leaked and (>=17 levels) the kmalloc'd/malloc'd
 *   _prop_stack_extern_elem entries (prop_stack.c:49-63) are leaked too.
 *
 * Modes:
 *   wedge  (default) โ€” control (types match -> add() OK) vs. buggy
 *                      (type mismatch at depth 1 -> prop_array_add()
 *                      blocks FOREVER on the leaked wrlock; proven by
 *                      fork+alarm(5): child exit 42 == still blocked).
 *   leak           โ€” 17-deep chains, type mismatch at the leaf: every
 *                      prop_array_equals() call leaks exactly one
 *                      malloc'd extern stack element; RSS climbs.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>

#include <libprop/proplib.h>

#define	EXIT_WEDGED	42	/* alarm fired while blocked in add() */

static void
on_alarm(int sig __unused)
{
	_exit(EXIT_WEDGED);
}

/*
 * Child: build a1=[leaf1], a2=[leaf2], run prop_array_equals(), then try
 * to WRITE a1 (prop_array_add takes the wrlock).  If the equals leaked
 * a1's rdlock, add() never returns and SIGALRM (5s) ends us with 42.
 */
static int
wedge_child(const char *leaf1, const char *leaf2)
{
	prop_array_t a1, a2;
	prop_object_t o1, o2;
	bool eq;

	a1 = prop_array_create();
	a2 = prop_array_create();
	if (leaf2 == NULL) {			/* type-mismatch variant */
		o1 = prop_string_create_cstring(leaf1);
		o2 = prop_number_create_integer(5);
	} else {				/* control: both strings */
		o1 = prop_string_create_cstring(leaf1);
		o2 = prop_string_create_cstring(leaf2);
	}
	prop_array_add(a1, o1);
	prop_array_add(a2, o2);
	prop_object_release(o1);
	prop_object_release(o2);

	fprintf(stderr, "  [child] built a1/a2, calling prop_array_equals...\n");
	eq = prop_array_equals(a1, a2);
	fprintf(stderr, "  [child] equals -> %d\n", eq);

	signal(SIGALRM, on_alarm);
	alarm(5);
	fprintf(stderr, "  [child] calling prop_array_add (wrlock on a1)...\n");
	fflush(stderr);
	if (prop_array_add(a1, prop_bool_create(true)) == false) {
		alarm(0);
		fprintf(stderr, "  [child] add returned FALSE (no wedge)\n");
		return (0);
	}
	alarm(0);
	fprintf(stderr, "  [child] add returned TRUE (no wedge)\n");
	return (0);
}

static int
run_wedge(const char *label, const char *leaf1, const char *leaf2)
{
	pid_t pid;
	int st;

	printf("== %s ==\n", label);
	fflush(stdout);
	pid = fork();
	if (pid == 0)
		_exit(wedge_child(leaf1, leaf2));
	waitpid(pid, &st, 0);
	if (WIFEXITED(st)) {
		int rc = WEXITSTATUS(st);
		if (rc == EXIT_WEDGED) {
			printf("   -> child STILL BLOCKED in prop_array_add "
			       "after 5s: rdlock LEAKED (exit 42)\n");
			return (1);
		}
		printf("   -> child exited %d: add() completed\n", rc);
		return (0);
	}
	printf("   -> child died abnormally: %d\n", st);
	return (2);
}

/*
 * Build a depth-@depth@ chain of arrays wrapping a final leaf.
 * type 0 -> string leaf, type 1 -> integer leaf.
 */
static prop_array_t
build_chain(int depth, int type)
{
	prop_array_t outer = NULL, cur = NULL;
	prop_object_t leaf;
	int i;

	leaf = type == 0 ? (prop_object_t)prop_string_create_cstring("s")
	                 : (prop_object_t)prop_number_create_integer(5);
	cur = (prop_array_t)leaf;
	for (i = 0; i < depth; i++) {
		outer = prop_array_create();
		prop_array_add(outer, cur);
		prop_object_release(cur);
		cur = outer;
	}
	return (cur);
}

static void
rss_checkpoint(const char *tag, int iters)
{
	struct rusage ru;

	getrusage(RUSAGE_SELF, &ru);
	printf("  [%s] after %d equals(): ru_maxrss = %ld kB\n",
	    tag, iters, ru.ru_maxrss);
}

static int
run_leak(int iters)
{
	prop_array_t a, b;
	int i;

	printf("== leak: 17-deep chains, type mismatch at leaf ==\n");
	printf("  (each prop_array_equals() abandons exactly one malloc'd"
	       " _prop_stack_extern_elem)\n");

	a = build_chain(17, 0);		/* [[[...["s"]...]]] */
	b = build_chain(17, 1);		/* [[[...[5]...]]]   */

	rss_checkpoint("start", 0);
	for (i = 1; i <= iters; i++) {
		(void)prop_array_equals(a, b);
		if (i % (iters / 4) == 0)
			rss_checkpoint("leak", i);
	}
	printf("   -> if ru_maxrss climbed ~ %d kB, the extern elements"
	       " leaked\n", iters * 48 / 1024);
	return (0);
}

int
main(int argc, char **argv)
{
	const char *mode = argc > 1 ? argv[1] : "wedge";
	int iters = argc > 2 ? atoi(argv[2]) : 400000;
	int rc = 0;

	if (strcmp(mode, "wedge") == 0) {
		rc |= run_wedge("control: a1=[\"x\"] vs a2=[\"y\"]"
		    " (types match, equals false by value)", "x", "y");
		rc |= run_wedge("buggy:   a1=[\"x\"] vs a2=[5]"
		    " (type mismatch at depth 1)", "x", NULL);
		printf("\nDF-5704 wedge result: %s\n",
		    rc & 1 ? "REPRODUCED (control unwedged, buggy wedged)"
		           : "not reproduced");
		return (rc);
	}
	if (strcmp(mode, "leak") == 0)
		return (run_leak(iters));
	fprintf(stderr, "usage: %s [wedge|leak [iters]]\n", argv[0]);
	return (64);
}