โฌข DragonFlyBSD Kernel Audit
DF-3154 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-3154 deterministic harness โ€” transcribes ext2_ei_csum_verify()'s
 * zero-inode test (sys/vfs/ext2fs/ext2_csum.c:583-590) verbatim:
 *
 *      static const struct ext2fs_dinode ei_zero;   // 160 bytes
 *      ...
 *      if (provided != calculated) {
 *              if (!memcmp(ei, &ei_zero, sizeof(struct ext2fs_dinode)))
 *                      return (0);
 *
 * sizeof(struct ext2fs_dinode) == 160 here (fields end at e2di_projid+4),
 * but the on-disk slot is EXT2_INODE_SIZE(fs) == 128 (mount-legal,
 * ext2_vfsops.c:539-545) and ext2_vget() hands ei = bp->b_data +
 * 128*ino_to_fsbo(ino) of a 1024-byte bread() buffer (ext2_vfsops.c:1372).
 *
 * Case A (slot idx 6): memcmp spans buffer bytes [768,928) โ€” the last
 *   32 bytes belong to the NEXT inode's slot (cross-inode read).
 * Case B (slot idx 7, last-in-block): bytes [1024,1056) lie 32 bytes
 *   PAST the buffer -> kernel-heap read; against a guard page: SIGSEGV.
 */
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define DINO_SIZE	160	/* sizeof(struct ext2fs_dinode), ext2_dinode.h */
#define ISIZE		128	/* EXT2_INODE_SIZE(fs), mount-legal */
#define BSIZE		1024	/* e2fs_bsize */
#define IPB		(BSIZE / ISIZE)

static char zero160[DINO_SIZE];		/* ei_zero */

static char *
guard_buf(size_t ps)
{
	char *two = mmap(NULL, ps * 2, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);

	if (two == MAP_FAILED) { perror("mmap"); exit(1); }
	if (mprotect(two + ps, ps, PROT_NONE)) { perror("mprotect"); exit(1); }
	return (two + ps - BSIZE);	/* 1024-byte buffer flush to guard */
}

int
main(void)
{
	size_t ps = (size_t)sysconf(_SC_PAGESIZE);
	char *buf = guard_buf(ps);
	int r, st;
	pid_t pid;

	printf("sizeof(struct ext2fs_dinode)=%d  EXT2_INODE_SIZE(fs)=%d  "
	    "bsize=%d ipb=%d\n", DINO_SIZE, ISIZE, BSIZE, IPB);
	printf("slot idx %d: memcmp range [%d,%d) -> %d bytes into NEXT slot\n",
	    IPB - 2, (IPB - 2) * ISIZE, (IPB - 2) * ISIZE + DINO_SIZE,
	    (IPB - 2) * ISIZE + DINO_SIZE - (IPB - 1) * ISIZE);
	printf("slot idx %d: memcmp range [%d,%d) -> %d bytes PAST buffer\n",
	    IPB - 1, (IPB - 1) * ISIZE, (IPB - 1) * ISIZE + DINO_SIZE,
	    (IPB - 1) * ISIZE + DINO_SIZE - BSIZE);

	/* ---- Case A: cross-slot read (in-buffer, deterministic) ---- */
	memset(buf, 0, BSIZE);
	memset(buf + (IPB - 1) * ISIZE, 0xAA, 32);	/* next slot head */
	r = memcmp(buf + (IPB - 2) * ISIZE, zero160, DINO_SIZE);
	printf("case A (next slot = 0xAA..): memcmp=%d -> EIO path\n", r);
	memset(buf + (IPB - 1) * ISIZE, 0x00, 32);
	r = memcmp(buf + (IPB - 2) * ISIZE, zero160, DINO_SIZE);
	printf("case A (next slot zeroed):  memcmp=%d -> csum error suppressed\n",
	    r);

	/* ---- Case B: read past the buffer (guard page) ---- */
	printf("case B (last slot, guard page after buffer): "
	    "fork+memcmp(slot %d, zero, %d) ...\n", IPB - 1, DINO_SIZE);
	fflush(stdout);
	pid = fork();
	if (pid == 0) {
		r = memcmp(buf + (IPB - 1) * ISIZE, zero160, DINO_SIZE);
		printf("case B: UNEXPECTED no-fault (memcmp=%d)\n", r);
		_exit(2);
	}
	waitpid(pid, &st, 0);
	if (WIFSIGNALED(st))
		printf("case B: child SIGSEGV'd as predicted "
		    "(signal %d) โ€” the %d-byte over-read crossed the buffer end\n",
		    WTERMSIG(st), (IPB - 1) * ISIZE + DINO_SIZE - BSIZE);
	else
		printf("case B: child exited %d\n", WEXITSTATUS(st));
	return (0);
}