⬢ DragonFlyBSD Kernel Audit
DF-5800 / forkcow.c
← back to finding ↓ download raw
/*
 * DF pass-3 vm_map.c stress rig 3 — fork COW churn.
 *
 * Exercises vmspace_fork / vmspace_fork_normal_entry / vm_map_copy_entry /
 * vm_map_backing_replicated / ONEMAPPING truncation in vm_map_delete:
 *  - fork with mixed wired (mlock'd) + shared + COW + NONE-inherit entries
 *  - children immediately mprotect RW and write (shadow path)
 *  - parent munmaps partial ranges (ONEMAPPING tail truncation,
 *    object->size shrink at vm_map.c:3362-3365) while children hold
 *    backing refs
 *  - re-fork from mutated map (backing_ba chain depth vs limit 5)
 *
 * build: cc -O -o forkcow forkcow.c -lpthread
 */
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#ifndef VM_INHERIT_NONE
#define VM_INHERIT_SHARE	0
#define VM_INHERIT_COPY	1
#define VM_INHERIT_NONE	2
#endif

#define	NREG	256
#define	REGSZ	(32 * 1024)

static void *regs[NREG];

int
main(void)
{
	int i, round, bad;
	pid_t pid;
	int status;
	unsigned char *p;

	for (i = 0; i < NREG; i++) {
		regs[i] = mmap(NULL, REGSZ, PROT_READ | PROT_WRITE,
			       MAP_ANON | MAP_PRIVATE, -1, 0);
		if (regs[i] == MAP_FAILED) {
			perror("mmap");
			return 1;
		}
		memset(regs[i], i & 0xff, REGSZ);
		switch (i & 3) {
		case 0:
			minherit(regs[i], REGSZ, VM_INHERIT_SHARE);
			break;
		case 1:
			minherit(regs[i], REGSZ, VM_INHERIT_NONE);
			break;
		case 2:
			/* leave COPY */
			mlock(regs[i], REGSZ); /* wired COW mix */
			break;
		}
	}

	for (round = 0; round < 60; round++) {
		pid = fork();
		if (pid == 0) {
			/* child: mutate a slice then fork a grandchild */
			for (i = 0; i < NREG; i += 7) {
				if ((i & 3) == 1)
					continue; /* INHERIT_NONE: absent */
				if (i & 1) {
					mprotect(regs[i], REGSZ,
						 PROT_READ | PROT_WRITE |
						 PROT_EXEC);
					memset(regs[i], 0xa3, REGSZ);
				} else {
					madvise(regs[i], REGSZ, MADV_DONTNEED);
				}
			}
			if (round & 1) {
				pid_t g = fork();
				if (g == 0) {
					for (i = 0; i < NREG; i += 13) {
						if ((i & 3) == 1)
							continue;
						memset(regs[i], 0x3c, REGSZ);
					}
					_exit(0);
				}
				waitpid(g, &status, 0);
			}
			_exit(0);
		}
		waitpid(pid, &status, 0);
		if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
			printf("round %d: child status %d sig %d\n", round,
			       WIFEXITED(status) ? WEXITSTATUS(status) : -1,
			       WTERMSIG(status));
			return 2;
		}
		/* parent: churn partial munmaps + remaps */
		for (i = round % 8; i < NREG; i += 32) {
			p = regs[i];
			munmap(p + REGSZ / 2, REGSZ / 4);
			regs[i] = mmap(p, REGSZ, PROT_READ | PROT_WRITE,
				       MAP_FIXED | MAP_ANON | MAP_PRIVATE,
				       -1, 0);
			if (regs[i] == MAP_FAILED) {
				perror("remap");
				return 3;
			}
		}
	}

	/* verify shareable regions still coherent */
	bad = 0;
	for (i = 0; i < NREG; i += 4)
		if (*(volatile unsigned char *)regs[i] != (i & 0xff) &&
		    *(volatile unsigned char *)regs[i] != 0xa3 &&
		    *(volatile unsigned char *)regs[i] != 0x3c &&
		    *(volatile unsigned char *)regs[i] != 0)
			bad++;
	printf("forkcow: complete, %d unexpected bytes\n", bad);
	return bad ? 4 : 0;
}