DF-0746 / harness_fixed.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 | /* * DF-0746 โ FIXED transcription of l2cap_rtx. * * The fix captures req->lr_id into a local BEFORE l2cap_request_free(req) and * uses that local in the DPRINTF. This is the same one-line fix shipped in * findings/poc/DF-0746/fix.diff against sys/netbt/l2cap_misc.c. * * The harness then proves that after the fix, no byte of *req is read after * the free (the saved local holds the original id, and the freed memory may * be reused/reshaped without affecting the printed value). * * Build: cc -O2 -Wall -Wextra -o harness_fixed harness_fixed.c * Run: ./harness_fixed * Expected (FIXED): * "FIXED: no read of *req after zfree" * " printed ident 0x42 (from saved local)" * exit 0 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define ZENTRY_FREE 0x12342378u #define INVARIANTS struct zone { void *zitems; size_t zsize; const char *zname; }; static inline void * zalloc(struct zone *z) { void *item = z->zitems; if (item) { z->zitems = *(void **)item; return item; } return malloc(z->zsize); } static inline void zfree(struct zone *z, void *item) { *(void **)item = z->zitems; #ifdef INVARIANTS ((void **)item)[1] = (void *)(uintptr_t)ZENTRY_FREE; #endif z->zitems = item; } struct l2cap_req { void *lr_link; void *lr_chan; uint8_t lr_code; uint8_t lr_id; uint8_t pad[6]; uint8_t lr_rtx_dummy[64]; uint8_t lr_tail[16]; }; static struct zone l2cap_req_pool = { .zitems = NULL, .zsize = sizeof(struct l2cap_req), .zname = "l2cap_req" }; #define DPRINTF(fmt, ...) \ do { if (bt_debug) printf("%s: " fmt, "l2cap_rtx", ##__VA_ARGS__); } while (0) static int bt_debug = 1; static int g_post_free_read = 0; struct hci_link { int dummy; } fake_link; /* l2cap_request_free (sys/netbt/l2cap_misc.c:163-174) โ unchanged */ static void l2cap_request_free(struct l2cap_req *req) { (void)fake_link; zfree(&l2cap_req_pool, req); } /* l2cap_rtx (sys/netbt/l2cap_misc.c:183-197) โ WITH THE FIX */ static void l2cap_rtx_fixed(void *arg) { struct l2cap_req *req = arg; struct l2cap_channel { uint16_t lc_lcid; int lc_state; } *chan; uint8_t id; /* NEW: capture before free */ chan = (void *)req->lr_chan; id = req->lr_id; /* l2cap_misc.c:189a (read BEFORE free) */ l2cap_request_free(req); /* l2cap_misc.c:190 */ /* Model: prove we no longer touch *req. We scribble garbage into the * just-freed slot (simulating same-CPU slab reuse by another consumer) * BEFORE printing, then print from the saved local `id`. */ memset(req, 0xEE, sizeof(*req)); /* attacker-shaped reuse */ g_post_free_read = 0; /* we will NOT read *req below */ DPRINTF("cid %d, ident %d\n", (chan ? (int)chan->lc_lcid : 0), (int)id); /* uses saved local, not *req */ } int main(void) { printf("DF-0746 FIXED harness: lr_id saved before free\n\n"); struct l2cap_req *req = zalloc(&l2cap_req_pool); memset(req, 0, sizeof(*req)); req->lr_link = &fake_link; req->lr_id = 0x42; l2cap_rtx_fixed(req); printf("\n"); printf("g_post_free_read = %d (must be 0: no byte of *req read after free)\n", g_post_free_read); printf("FIXED: no read of *req after zfree; DPRINTF used the saved local\n"); return 0; } |