DF-2672 / leak_fork.c
/* * DF-2672 - vm_object reference leak: fork() of a wired (mlock'd) entry. * * vm_map_backing_replicated() (sys/vm/vm_map.c:3512) takes a reference on * the base object for the child's cloned entry, then vm_map_copy_entry() * (sys/vm/vm_map.c:3627-3634) hits the "src_entry->wired_count" path, * detaches and NULLs dst_entry->ba.map_object WITHOUT dropping that * reference. The reference is orphaned; when both processes exit the * object survives forever (kernel memory + swap-backed pages never freed). * * usage: leak_fork <iterations> <bytes-per-iter> leak|control * leak - mmap+dirty+mlock, fork(), both exit (leaks 1 vm_object ref) * control - mmap+dirty+mlock, no fork, exit (object properly freed) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> #include <sys/wait.h> int main(int argc, char **argv) { long iters = (argc > 1) ? atol(argv[1]) : 100; size_t bytes = (argc > 2) ? (size_t)atol(argv[2]) : (256 * 1024); int do_fork = (argc > 3 && strcmp(argv[3], "control") == 0) ? 0 : 1; long i; if (bytes & 4095UL) bytes = (bytes + 4095) & ~4095UL; for (i = 0; i < iters; i++) { pid_t p = fork(); if (p < 0) { perror("fork"); exit(1); } if (p == 0) { char *m; pid_t c; m = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (m == MAP_FAILED) _exit(1); memset(m, 0x41, bytes); /* dirty the pages */ if (mlock(m, bytes) != 0) { perror("mlock"); _exit(2); } if (do_fork) { c = fork(); /* THE BUG: fork wired entry */ if (c == 0) _exit(0); /* child */ if (wait(NULL) != c) _exit(3); } _exit(0); /* region owner exits */ } wait(NULL); } fprintf(stderr, "%s: done %ld iters x %zu bytes (%s)\n", argv[0], iters, bytes, do_fork ? "LEAK" : "control"); return (0); } |