⬢ DragonFlyBSD Kernel Audit
DF-4865 / cpuctl_probe.c
← back to finding ↓ download raw
/*
 * DF-4865 pass-2 live probe for sys/dev/misc/cpuctl/cpuctl.c
 *
 * Documents, on the running guest:
 *   1. /dev/cpuctl0 permission gate (unprivileged user cannot open at all)
 *   2. CPUCTL_RDMSR on a valid MSR (MSR_BIOS_SIGN 0x8B) succeeds
 *   3. CPUCTL_RDMSR / CPUCTL_WRMSR / CPUCTL_MSRSBIT / CPUCTL_MSRCBIT on an
 *      architecturally-invalid MSR return EFAULT and DO NOT panic
 *      (rdmsr_safe/wrmsr_safe + msr_onfault path -- the DF-4735/DF-4825
 *      #GP family does NOT apply to the cpuctl ioctl path)
 *   4. CPUCTL_CPUID / CPUCTL_CPUID_COUNT return zeroed, well-formed data
 *   5. CPUCTL_UPDATE argument validation (NULL/0-size -> EINVAL) and a
 *      garbage-blob update attempt -> the "updated microcode from rev=X to
 *      rev=Y" kprintf carries *initialized* values on this CPU
 *      (MSR_BIOS_SIGN reads fine under QEMU, so the uninit-print branch of
 *      DF-4865 cannot be triggered on this guest -> not_reproduced here)
 *
 * cc -O -o /tmp/cpuctl_probe /tmp/cpuctl_probe.c && /tmp/cpuctl_probe
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cpuctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

static int
rdmsr(int fd, int msr, uint64_t *out)
{
	cpuctl_msr_args_t a;
	int r;

	memset(&a, 0xa5, sizeof(a));	/* poison: detect stale writes */
	a.msr = msr;
	r = ioctl(fd, CPUCTL_RDMSR, &a);
	if (r == 0 && out)
		*out = a.data;
	return (r);
}

int
main(void)
{
	cpuctl_msr_args_t a;
	cpuctl_cpuid_args_t cid;
	cpuctl_cpuid_count_args_t cc;
	cpuctl_update_args_t up;
	uint64_t v;
	int fd, r, i;

	printf("== 1. unprivileged open gate ==\n");
	fd = open("/dev/cpuctl0", O_RDONLY);
	if (fd < 0)
		printf("open(O_RDONLY) as this user: %s (errno=%d)\n",
		    strerror(errno), errno);
	else
		printf("open(O_RDONLY) OK -- user CAN read\n");

	fd = open("/dev/cpuctl0", O_RDWR);
	if (fd < 0) {
		printf("open(O_RDWR): %s (errno=%d)\n", strerror(errno), errno);
		fd = open("/dev/cpuctl0", O_RDONLY);
		if (fd < 0) {
			printf("cannot open at all: %s\n", strerror(errno));
			return (1);
		}
	}

	printf("\n== 2. RDMSR valid (MSR_BIOS_SIGN 0x8b) ==\n");
	r = rdmsr(fd, 0x8b, &v);
	printf("rdmsr(0x8b) = %d data=0x%016jx errno=%d (%s)\n",
	    r, (uintmax_t)v, errno, r ? strerror(errno) : "ok");

	printf("\n== 3. safe-fault family on invalid MSR 0x11111111 ==\n");
	r = rdmsr(fd, 0x11111111, &v);
	printf("rdmsr_safe path: ret=%d errno=%d (%s) data=0x%016jx\n",
	    r, errno, strerror(errno), (uintmax_t)v);

	memset(&a, 0, sizeof(a));
	a.msr = 0x11111111;
	a.data = 0x4141414141414141ULL;
	r = ioctl(fd, CPUCTL_WRMSR, &a);
	printf("wrmsr_safe path: ret=%d errno=%d (%s)\n", r, errno,
	    r ? strerror(errno) : "ok");

	memset(&a, 0, sizeof(a));
	a.msr = 0x11111111;
	a.data = 0x1;
	r = ioctl(fd, CPUCTL_MSRSBIT, &a);
	printf("msrsbit_safe path: ret=%d errno=%d (%s)\n", r, errno,
	    r ? strerror(errno) : "ok");

	memset(&a, 0, sizeof(a));
	a.msr = 0x11111111;
	a.data = 0x1;
	r = ioctl(fd, CPUCTL_MSRCBIT, &a);
	printf("msrcbit_safe path: ret=%d errno=%d (%s)\n", r, errno,
	    r ? strerror(errno) : "ok");

	printf("guest still alive after safe-fault family\n");

	printf("\n== 4. CPUID framing ==\n");
	memset(&cid, 0xa5, sizeof(cid));
	cid.level = 1;
	r = ioctl(fd, CPUCTL_CPUID, &cid);
	printf("cpuid(1): ret=%d eax=0x%08x ebx=0x%08x ecx=0x%08x edx=0x%08x\n",
	    r, cid.data[0], cid.data[1], cid.data[2], cid.data[3]);
	memset(&cid, 0xa5, sizeof(cid));
	cid.level = 0x7ffffff9;		/* invalid level */
	r = ioctl(fd, CPUCTL_CPUID, &cid);
	printf("cpuid(0x7ffffff9): ret=%d data=%08x/%08x/%08x/%08x (no stale 0xa5a5a5a5)\n",
	    r, cid.data[0], cid.data[1], cid.data[2], cid.data[3]);
	memset(&cc, 0xa5, sizeof(cc));
	cc.level = 4;			/* deterministic leaf: needs subleaf */
	cc.level_type = 0;
	r = ioctl(fd, CPUCTL_CPUID_COUNT, &cc);
	printf("cpuid_count(4,0): ret=%d data=%08x/%08x/%08x/%08x\n",
	    r, cc.data[0], cc.data[1], cc.data[2], cc.data[3]);

	printf("\n== 5. UPDATE arg validation + garbage blob (root only) ==\n");
	memset(&up, 0, sizeof(up));
	up.data = NULL;
	up.size = 0;
	r = ioctl(fd, CPUCTL_UPDATE, &up);
	printf("update(NULL,0): ret=%d errno=%d (%s)\n", r, errno,
	    r ? strerror(errno) : "ok");

	up.data = (void *)1;		/* unmapped -> copyin fault */
	up.size = 64;
	r = ioctl(fd, CPUCTL_UPDATE, &up);
	printf("update(bad ptr,64): ret=%d errno=%d (%s)\n", r, errno,
	    r ? strerror(errno) : "ok");

	{
		unsigned char blob[64];
		memset(blob, 0x5a, sizeof(blob));
		up.data = blob;
		up.size = sizeof(blob);
		r = ioctl(fd, CPUCTL_UPDATE, &up);
		printf("update(garbage,64): ret=%d errno=%d (%s)\n", r, errno,
		    r ? strerror(errno) : "ok");
	}
	printf("done\n");
	return (0);
}