⬢ DragonFlyBSD Kernel Audit
DF-5693 / race_bool.c
← back to finding ↓ download raw
/*
 * DF-5693 — race_bool.c
 *
 * Differential harness for the *kernel* flavor of _PROP_ONCE_RUN racing
 * the lazy singleton init in sys/libprop/prop_bool.c.
 *
 * The kernel macro (sys/libprop/prop_object_impl.h:302-303) is
 *
 *   #define _PROP_ONCE_DECL(x)   static int x = 0;
 *   #define _PROP_ONCE_RUN(x,f)  if (atomic_cmpset_int(&(x), 0, 1)) f()
 *
 * — the completion flag flips to 1 BEFORE f() runs, and the cmpset loser
 * does NOT wait.  For prop_bool the raced f() is _prop_bool_init()
 * (prop_bool.c:111-124), which stores po_type and pb_value into the two
 * BSS singletons _prop_bool_true / _prop_bool_false (prop_bool.c:40-41).
 * There is NO boot-time kernel consumer of prop_bool (the only in-kernel
 * reachability is plist internalize of <true/>/<false/> via ioctls such
 * as device-mapper), so the first-ever use is attacker-triggerable and
 * the cmpset window is live at that moment.
 *
 * What the loser can observe (with a widened window):
 *   state A:  po_type == NULL, pb_value == false
 *             -> prop_bool_true(&_prop_bool_true) guard fails => FALSE
 *                prop_bool_copy(b) guard fails => NULL
 *                prop_object_release(b) => po->po_type->pot_lock
 *                (prop_object.c:1085) => NULL-pointer read => SIGSEGV
 *   state B:  po_type set, pb_value still false (between lines 116/117)
 *             -> object *looks* typed but prop_bool_true(b) == false:
 *                the TRUE singleton lies about its value.
 *
 * Why this is filed Info and not higher: at REALISTIC timing the window
 * is a handful of stores and the loser's in-window path
 * (_prop_bool_alloc body, prop_bool.c:132-135 -> prop_object_retain ->
 * atomic_inc_32_nv on po_refcnt, prop_object.c:987-994) reads NO
 * initialized field; every po_type/pb_value read happens long after the
 * winner's stores became globally visible.  Section 6 of the battery
 * (300 fresh processes, no widening) demonstrates exactly that: 0/300
 * anomalies.  The widening knobs are rig instrumentation only.
 *
 * Library variants (build.sh):
 *   race_bool       -> stock /usr/src/sys/libprop (userland once is
 *                      pthread_once, HAS completion semantics). CONTROL.
 *   race_bool_ksem  -> same sources, userland once swapped for a faithful
 *                      port of the _KERNEL cmpset-once + env-controlled
 *                      window widening inside _prop_bool_init.
 *
 * Modes:  [check]      default: create both singletons, verify the
 *                       value/identity/copy invariants, then release.
 *          release     skip checks, create + release immediately (crash
 *                       flavor: NULL po_type -> SIGSEGV in release).
 *          warmup      complete the once single-threaded first (= the
 *                       SYSINIT(SI_SUB_PROP) bootstrap of fix.diff).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <libprop/prop_bool.h>
#include <libprop/prop_object.h>

#define NTHR 8

static pthread_barrier_t bar;
static int mode_release;
static int violations;

static void
report(const char *what, const char *state)
{
	fprintf(stderr, "VIOLATION[%s]: %s\n", state, what);
	violations = 1;
}

static void *
worker(void *arg)
{
	prop_bool_t b, f, bc;

	pthread_barrier_wait(&bar);	/* first-ever prop_bool use, all at once */

	b = prop_bool_create(true);
	f = prop_bool_create(false);

	if (mode_release) {
		/* crash flavor: release while the winner may still be
		 * inside _prop_bool_init (state A) -> po_type NULL ->
		 * SIGSEGV at prop_object.c:1085 (po->po_type->pot_lock). */
		prop_object_release(b);
		prop_object_release(f);
		return (NULL);
	}

	if (b == NULL || f == NULL || b == f) {
		report("create returned NULL or both singletons identical",
		       "A:po_type=NULL");
		goto out;
	}
	if (prop_bool_true(b) != true) {
		/* distinguish mid-init states for the log */
		if (prop_bool_copy(b) == NULL)
			report("prop_bool_true(_prop_bool_true) == false "
			       "(guard failed: po_type not yet stored)",
			       "A:po_type=NULL");
		else
			report("prop_bool_true(_prop_bool_true) == false "
			       "(typed object, pb_value stale)",
			       "B:value=stale");
		goto out;
	}
	if (prop_bool_true(f) != false) {
		report("prop_bool_true(_prop_bool_false) == true", "B:value=stale");
		goto out;
	}
	bc = prop_bool_copy(b);
	if (bc == NULL) {
		report("prop_bool_copy failed (guard)", "A:po_type=NULL");
		goto out;
	}
	if (bc != b || !prop_bool_equals(b, bc)) {
		report("singleton identity broken", "A/B");
		prop_object_release(bc);
		goto out;
	}
	prop_object_release(bc);
out:
	prop_object_release(b);
	prop_object_release(f);
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t th[NTHR];
	int warmup = 0, i;

	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "warmup") == 0)
			warmup = 1;
		else if (strcmp(argv[i], "release") == 0)
			mode_release = 1;
	}

	if (warmup) {
		/* Emulates fix.diff: SYSINIT(SI_SUB_PROP) completes the lazy
		 * init single-threaded BEFORE any concurrent first use. */
		prop_bool_t p0 = prop_bool_create(true);
		prop_object_release(p0);
		printf("warmup: lazy init completed single-threaded\n");
	}

	pthread_barrier_init(&bar, NULL, NTHR);
	for (i = 0; i < NTHR; i++)
		pthread_create(&th[i], NULL, worker, NULL);
	for (i = 0; i < NTHR; i++)
		pthread_join(th[i], NULL);

	if (violations) {
		fprintf(stderr, "FAILED: singleton init invariant broken\n");
		return (3);
	}
	printf("SURVIVED: both singletons typed, values correct, "
	    "identity held (%d threads)\n", NTHR);
	return (0);
}