โฌข DragonFlyBSD Kernel Audit
DF-1093 / df1093_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1093 โ€” userspace harness for ppb_pnp_detect NULL+1 deref.
 *
 * The kernel path (sys/bus/ppbus/ppbconf.c:ppb_pnp_detect) is reachable
 * only when an IEEE 1284 peripheral is electrically attached to a parallel
 * port and supplies a PnP ID string at attach time. The audit QEMU guest
 * has no parallel port (no lpt/ppbus device in dmesg), so this harness
 * mirrors the algorithm to demonstrate the NULL+1 โ†’ deref crash pattern.
 *
 * A malicious peripheral sends "MFG\0" (keyword found, no ':' before the
 * next NUL). search_token() returns NULL for the ':' lookup. ppb_pnp_detect
 * unconditionally computes NULL + 1 = (char*)0x1 and passes it to
 * kprintf("%s", ...) which dereferences address 1 โ†’ SIGSEGV / kernel
 * page fault / panic.
 *
 * The harness installs a SIGSEGV handler that prints "BUG: NULL+1
 * dereferenced" so the crash is observable without taking down the host.
 *
 * Build:  cc -O0 -o df1093_harness df1093_harness.c
 *   or:   cc -O0 -DFIX -o df1093_harness_fix df1093_harness.c
 * Run:    ./df1093_harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>

#define UNKNOWN_LENGTH -1

static sigjmp_buf jmp_env;
static volatile sig_atomic_t got_segfault;

static void
sigsegv_handler(int sig)
{
	(void)sig;
	got_segfault = 1;
	siglongjmp(jmp_env, 1);
}

/* Mirror search_token at ppbconf.c:177-206. Returns pointer to match, or NULL. */
static char *
search_token(char *str, int slen, char *token)
{
	char *p;
	int tlen, i, j;

	if (slen == UNKNOWN_LENGTH)
		for (slen = 0, p = str; *p != '\0'; p++)
			slen++;

	for (tlen = 0, p = token; *p != '\0'; p++)
		tlen++;

	if (tlen == 0)
		return (str);

	for (i = 0; i <= slen - tlen; i++) {
		for (j = 0; j < tlen; j++)
			if (str[i + j] != token[j])
				break;
		if (j == tlen)
			return (&str[i]);
	}
	return (NULL);
}

/* Mirror ppb_pnp_detect's MFG-handling block โ€” the bug.
 * 'str' is the IEEE 1284 PnP string. */
static void
detect_mfg(char *str, int len)
{
	char *token, *val;

	if ((token = search_token(str, len, "MFG")) != NULL) {
		/* keyword found; look for ':' after it */
		val = search_token(token, UNKNOWN_LENGTH, ":");
#ifndef FIX
		/* UNPATCHED โ€” NULL + 1 unconditionally, then deref via %s */
		char *p = val + 1; /* val may be NULL โ†’ p = (char*)0x1 */
		/* trigger the deref (this is what kprintf("%s", p) does) */
		(void)*(volatile char *)p;
		printf("  MFG value: '%s' (would print)\n", p);
#else
		/* PATCHED โ€” check val for NULL before deref */
		if (val != NULL) {
			char *p = val + 1;
			(void)*(volatile char *)p;
			printf("  MFG value: '%s'\n", p);
		} else {
			printf("  MFG keyword found but no ':' โ€” skip safely\n");
		}
#endif
	} else {
		printf("  no MFG keyword\n");
	}
}

int
main(void)
{
	struct sigaction sa;
	char str_mfg_only[] = "MFG"; /* keyword, no ':' */

	printf("=== DF-1093 ppb_pnp_detect NULL+1 harness (%s) ===\n",
#ifdef FIX
	    "PATCHED"
#else
	    "UNPATCHED"
#endif
	    );

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sigsegv_handler;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGSEGV, &sa, NULL);

	got_segfault = 0;
	if (sigsetjmp(jmp_env, 1) == 0) {
		detect_mfg(str_mfg_only, strlen(str_mfg_only));
		printf("  returned normally\n");
	} else {
		printf("  BUG: NULL+1 deref trapped (SIGSEGV at addr 1)\n");
		printf("  ===== In the kernel: page fault, kernel panic at boot =====\n");
	}

	return (got_segfault ? 0 : 0);
}