DragonFlyBSD Kernel Audit
DF-0035 / dump_msgbuf.c
← back to finding ↓ download raw
/* Dump msgbuf internals via kvm(3) for DF-0035 verification.
 * Needs /dev/kmem read access (root).  Reports msg_magic/size/bufx/bufr/bufl
 * and computes the sysctl_kern_msgbuf branch decision so we can see whether
 * branch 3 (the buggy one) is even reachable in the current state.
 */
#include <sys/types.h>
#include <sys/msgbuf.h>
#include <sys/param.h>
#include <fcntl.h>
#include <kvm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <nlist.h>

static struct nlist nl[] = {
	{ .n_name = "msgbufp" },
	{ .n_name = NULL }
};

int
main(int argc, char **argv)
{
	kvm_t *kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, "kvm_open");
	if (!kd) { fprintf(stderr, "kvm_openfiles failed\n"); return 1; }
	if (kvm_nlist(kd, nl) != 0 || nl[0].n_value == 0) {
		fprintf(stderr, "nlist msgbufp failed\n");
		kvm_close(kd);
		return 1;
	}
	/* msgbufp is `struct msgbuf *`; read the pointer first. */
	struct msgbuf *mbp_ptr;
	if (kvm_read(kd, nl[0].n_value, &mbp_ptr, sizeof(mbp_ptr)) != sizeof(mbp_ptr)) {
		fprintf(stderr, "kvm_read pointer failed: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		return 1;
	}
	printf("msgbufp (kernel pointer) = %p\n", (void *)mbp_ptr);

	struct msgbuf mb;
	if (kvm_read(kd, (unsigned long)mbp_ptr, &mb, sizeof(mb)) != sizeof(mb)) {
		fprintf(stderr, "kvm_read msgbuf failed: %s\n", kvm_geterr(kd));
		kvm_close(kd);
		return 1;
	}
	printf("msg_magic   = 0x%08x (expect 0x%08x)\n", mb.msg_magic, (unsigned)MSG_MAGIC);
	printf("msg_size    = %u\n", mb.msg_size);
	printf("msg_ptr     = %p\n", (void *)mb.msg_ptr);
	printf("msg_bufx    = %u  (mod size = %u)\n", mb.msg_bufx, mb.msg_bufx % mb.msg_size);
	printf("msg_bufr    = %u  (mod size = %u)\n", mb.msg_bufr, mb.msg_bufr % mb.msg_size);
	printf("msg_bufl    = %u  (mod size = %u)\n", mb.msg_bufl, mb.msg_bufl % mb.msg_size);

	/* Simulate sysctl_kern_msgbuf's branch decision. */
	u_int rindex = mb.msg_bufr;
	u_int xindex = mb.msg_bufx;
	u_int n = xindex - rindex;
	int shrank = 0;
	if (n > mb.msg_size - 1024) {
		rindex = xindex - mb.msg_size + 2048;
		n = xindex - rindex;
		shrank = 1;
	}
	u_int rindex_modulo = rindex % mb.msg_size;
	u_int xindex_modulo = xindex % mb.msg_size;
	int branch;
	u_int out_off, out_len_buggy, out_len_correct;
	if (rindex_modulo < xindex_modulo) {
		branch = 1;
		out_off = rindex_modulo; out_len_buggy = out_len_correct = xindex_modulo - rindex_modulo;
	} else if (rindex_modulo == xindex_modulo) {
		branch = 2;
		out_off = 0; out_len_buggy = out_len_correct = 1;  /* the "\n" */
	} else if (n <= mb.msg_size - rindex_modulo) {
		branch = 3;
		out_off = rindex_modulo;
		out_len_correct = n;
		out_len_buggy = n - rindex_modulo;   /* THE BUG */
	} else {
		branch = 4;
		out_off = rindex_modulo;
		out_len_correct = n;
		out_len_buggy = mb.msg_size - rindex_modulo; /* first half; second half uses n */
	}
	printf("\n-- sysctl_kern_msgbuf decision --\n");
	printf("shrank     = %d\n", shrank);
	printf("n          = %u\n", n);
	printf("rindex_mod = %u   xindex_mod = %u\n", rindex_modulo, xindex_modulo);
	printf("BRANCH     = %d\n", branch);
	printf("out_off    = %u (from msg_ptr)\n", out_off);
	printf("out_len(correct) = %u\n", out_len_correct);
	if (branch == 3) {
		printf("out_len(buggy)   = %u   (n - rindex_modulo)\n", out_len_buggy);
		if (rindex_modulo > n) {
			printf(">>> UNDERFLOW! rindex_modulo(%u) > n(%u) -- OOB read of ~%u bytes past msg_ptr+msg_size <<<\n",
			    rindex_modulo, n, out_len_buggy);
		} else {
			printf("rindex_modulo(%u) <= n(%u): no underflow (just a %d-byte under-read)\n",
			    rindex_modulo, n, (int)(out_len_correct - out_len_buggy));
		}
	}
	kvm_close(kd);
	return 0;
}