DF-0149 / tlv_truncation.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 | /* * DF-0149 - Signed-integer truncation in preload TLV walk size math. * * sys/kern/subr_module.c, the per-record advance used by every * preload_search_*() / preload_delete_name() / preload_bootstrap_relocate(): * * int next; // SIGNED int * ... * next = sizeof(u_int32_t) * 2 + hdr[1]; // hdr[1] is u_int32_t * next = roundup(next, sizeof(u_long)); // roundup is division-based * curp += next; // caddr_t += int * * roundup(x,y) = ((((x)+((y)-1))/(y))*(y)) (sys/sys/param.h:402) is * sign-preserving, so a negative `next` stays negative and `curp` walks * BACKWARD past preload_metadata, reading/writing memory that precedes the * metadata blob. hdr[1]==0xFFFFFFF8 makes next==0 -> infinite loop / boot hang. * * This harness reproduces the exact C arithmetic for a range of crafted * hdr[1] values and prints whether next goes negative / zero / OOB. * * Boot-time only: preload_metadata is supplied by the boot loader from the * kernel + preloaded-module image. Triggering requires a tampered/crafted * preloaded module or bootloader compromise (high privilege). Not reachable * from unprivileged userspace at runtime. */ #include <stdio.h> #include <stdint.h> #include <stddef.h> /* Exact kernel definitions. */ typedef uint32_t u_int32_t; typedef unsigned long u_long; #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* sys/sys/param.h:402 */ #define MODINFO_END 0 static void walk_emulation(u_int32_t *fake_meta, size_t words, const char *label) { unsigned char *curp = (unsigned char *)fake_meta; unsigned char *base = curp; unsigned char *blob_end = base + words * sizeof(u_int32_t); int step = 0; printf("=== %s (metadata @ %p, %zu words) ===\n", label, (void *)base, words); for (;;) { u_int32_t *hdr = (u_int32_t *)curp; int next; /* Bounds-checked read: never dereference outside the blob (the real * kernel does NOT do this check -- that is precisely the bug). */ if (curp < base || curp + 2*sizeof(u_int32_t) > blob_end) { long off = (long)(curp - base); printf(" step %d: WOULD READ at offset %+ld -- %s the %zu-byte blob\n", step, off, off < 0 ? "*** BEFORE ***" : "*** PAST ***", words * sizeof(u_int32_t)); if (off < 0) printf(" => kernel reads memory that PRECEES preload_metadata " "(OOB read), or loops here forever (next==0)\n"); break; } if (hdr[0] == MODINFO_END && hdr[1] == MODINFO_END) { printf(" step %d: terminator reached normally at offset %+ld\n", step, (long)(curp - base)); break; } next = sizeof(u_int32_t) * 2 + hdr[1]; next = roundup(next, sizeof(u_long)); printf(" step %d: type=0x%08x len(hdr[1])=0x%08x -> next(int)=%d (0x%x) " "[offset %+ld]\n", step, hdr[0], hdr[1], next, (unsigned)next, (long)(curp - base)); if (next <= 0) { if (next == 0) printf(" !!! next==0 => curp never advances => INFINITE LOOP " "(boot hang)\n"); else printf(" !!! next<0 => curp walks BACKWARD past preload_metadata " "=> OOB read before blob\n"); } curp += next; step++; if (step > 8) { printf(" ... (stopping after 8 steps)\n"); break; } } printf("\n"); } int main(void) { /* * Case A: a benign metadata blob. type=1(MODINFO_NAME), len=5 ("hello"+nul). * Walks forward correctly, hits terminator. */ u_int32_t good[6]; good[0] = 1; /* MODINFO_NAME */ good[1] = 6; /* len bytes */ good[2] = 0x00006c6c; /* "ll" + nul + pad */ good[3] = 0; good[4] = 0; /* terminator */ good[5] = 0; walk_emulation(good, 6, "A: benign metadata (forward walk OK)"); /* * Case B: tampered len = 0x80000000. 8 + 0x80000000 (as size_t) truncates * to int 0x80000008 = negative. roundup keeps it negative. curp goes * BACKWARD. */ u_int32_t bad1[4]; bad1[0] = 1; bad1[1] = 0x80000000; bad1[2] = 0; bad1[3] = 0; walk_emulation(bad1, 4, "B: hdr[1]=0x80000000 -> next NEGATIVE (backward walk)"); /* * Case C: tampered len = 0xFFFFFFF8. 8 + 0xFFFFFFF8 = 0x100000000 (size_t), * truncated to int = 0 -> roundup(0,8)=0 -> next==0 -> infinite loop. */ u_int32_t bad2[4]; bad2[0] = 1; bad2[1] = 0xFFFFFFF8; bad2[2] = 0; bad2[3] = 0; walk_emulation(bad2, 4, "C: hdr[1]=0xFFFFFFF8 -> next==0 (boot hang)"); return 0; } |