DF-1046 / 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 | /* * DF-1046 userspace harness โ replicates the EXACT loop semantics of * sys/bus/u4b/uvc/uvc_ctrls.c:917-978 (uvc_ctrl_init_dev) and proves the * uint8_t-loop-counter wrap is an unbounded-write primitive. * * cc -DBUGGY -O2 -o harness_buggy harness.c # current kernel: uint8_t i * cc -O2 -o harness_fixed harness.c # proposed fix: unsigned int i * * The "buggy" build uses uint8_t for `i` (as the kernel does today at line 925); * the "fixed" build uses unsigned int (the proposed fix). The buggy build will * (a) loop ~forever, and (b) write far past the nctrls-sized array. * * ITER_CAP is enforced so the harness finishes; the kernel has no such cap. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define UVC_BITSHIFT 3 #define UVC_BITMASK 0x7 #define UVC_VALMASK 0x1 static int uvc_test_bit(const uint8_t *buf, int b) { return (buf[b >> UVC_BITSHIFT] >> (b & UVC_BITMASK)) & UVC_VALMASK; } /* Mirror of uvc_ctrl_count_control โ uses `int i` (the CORRECT width). */ static unsigned int count_control(const uint8_t *bm, unsigned int bs) { int i; unsigned int n = 0; for (i = 0; i < (int)(bs * 8); i++) if ((bm[i >> UVC_BITSHIFT] >> (i & UVC_BITMASK)) & UVC_VALMASK) n++; return n; } #define ITER_CAP 2000000 /* kill-switch; the kernel has none */ #define OOB_CAP 4096 /* bail after this many OOB writes (proof enough) */ #define SLOT 64 /* stand-in sizeof(struct uvc_control) */ int main(int argc, char **argv) { unsigned int bCtrlSize = (argc > 1) ? (unsigned)strtoul(argv[1], NULL, 0) : 32; unsigned int nctrls; uint8_t *bm, *sink; unsigned int sink_bytes; unsigned long writes_in = 0, writes_oob = 0, iters = 0; int oob_first = -1; if (bCtrlSize == 0) { fprintf(stderr, "bCtrlSize=0: nothing to do\n"); return 0; } /* bmControls: bCtrlSize bytes, only bit 0 set (Brightness) */ bm = calloc(bCtrlSize, 1); if (!bm) { perror("calloc bm"); return 2; } bm[0] = 0x01; nctrls = count_control(bm, bCtrlSize); printf("bCtrlSize=%u bCtrlSize*8=%u nctrls(set bits)=%u\n", bCtrlSize, bCtrlSize * 8u, nctrls); /* Mirror of kmalloc(nctrls * sizeof(struct uvc_control)). Generous * padding (16 MB) so OOB writes don't immediately segfault โ we want * to COUNT them, not just crash. The kernel has no such luxury: the * OOB writes corrupt adjacent slab objects until something page-faults. */ sink_bytes = nctrls * (unsigned)SLOT; sink = calloc(sink_bytes + (16u << 20), 1); if (!sink) { perror("calloc sink"); return 2; } memset(sink + sink_bytes, 0xAA, (16u << 20)); /* The init loop โ structurally identical to uvc_ctrls.c:963-974. * IMPORTANT: do NOT cast the loop bound. In the kernel both operands * promote to int, so the bound stays at bCtrlSize*8 (e.g. 256) while * only `i` is narrow. That asymmetry IS the bug. */ { uint8_t *ctrl = sink; /* topo_node->controls */ #ifdef BUGGY uint8_t i; /* THE BUG (uvc_ctrls.c:925) */ #else unsigned int i; /* the fix */ #endif for (i = 0; i < bCtrlSize * 8u; i++) { if (uvc_test_bit(bm, i) == 0) continue; /* uvc_ctrl_initialize_control(ctrl) would kmalloc uvc_data etc. * here. We just stamp the slot to detect OOB. */ if ((uintptr_t)(ctrl - sink) < sink_bytes) writes_in++; else { if (oob_first < 0) oob_first = (int)iters; writes_oob++; } *ctrl = 0xCC; ctrl += SLOT; /* ctrl++ */ iters++; if (iters >= ITER_CAP) break; if (writes_oob >= OOB_CAP) { iters |= 0x80000000UL; break; } } } printf("iterations executed: %lu (cap=%d)\n", iters, ITER_CAP); printf("writes IN-BOUNDS : %lu (expected nctrls=%u)\n", writes_in, nctrls); printf("writes OUT-OF-BOUNDS: %lu%s\n", writes_oob, writes_oob ? " <-- UNBOUNDED HEAP OVERFLOW (primitive confirmed)" : ""); if (oob_first >= 0) printf("first OOB write at iter %d (i had wrapped at least once)\n", oob_first); #ifdef BUGGY if (bCtrlSize >= 32 && (writes_oob > 0 || iters >= ITER_CAP)) { printf("\nVERDICT: BUGGY โ uint8_t i wraps when bCtrlSize*8 > 255;\n" " the loop fails to terminate and writes struct uvc_control\n" " entries (with pointers / nested kmallocs) past the allocation.\n"); return 0; } #else if (writes_oob == 0 && writes_in == nctrls) { printf("\nVERDICT: FIXED โ unsigned int i holds the full range; loop\n" " terminates after exactly nctrls in-bounds writes.\n"); return 0; } #endif printf("\nVERDICT: unexpected โ investigate.\n"); return 1; } |