DF-0832 / udf_oob_kmod.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 | /* * DF-0832 — In-kernel deterministic harness for the off-by-one OOB read * in udf_bmap_internal (sys/vfs/udf/udf_vnops.c:1104 / :1128). * * This kernel module replicates the EXACT buggy do/while loop from * udf_bmap_internal and runs it against a controlled struct file_entry * allocation with a guard sentinel placed right past the live AD area. * It detects whether the loop dereferences past l_ad (the off-by-one) * and prints the result. * * Build (in-guest, as root): * cd /usr/src/sys && make -V SYSDIR|xargs -I{} cc -DKERNEL_TEST \ * -c /root/udf_oob_kmod.c -I{} -o /tmp/udf_oob_kmod.o # (fiddly) * * Simpler: build as a loadable kld module. Save this file as * /root/df0832_oob/udf_oob.c and the Makefile below as * /root/df0832_oob/Makefile, then `make && kldload ./udf_oob.ko`. * * Makefile: * .PATH: /root/df0832_oob * KMOD= udf_oob * SRCS= udf_oob.c * .include <bsd.kmod.mk> * * Expected output (bug present): dmesg shows * "DF-0832: OOB READ DETECTED at ad_offset=8 l_ad=8 ..." * Expected (bug fixed, >= instead of >): dmesg shows * "DF-0832: no OOB (clean EINVAL at ad_offset=8)" */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/module.h> #include <sys/sysctl.h> MALLOC_DECLARE(M_UDFFENTRY); MALLOC_DEFINE(M_UDFFENTRY, "UDF fentry harness", "DF-0832 harness"); #include <vfs/udf/ecma167-udf.h> #define SHORT_AD_SIZE (sizeof(struct short_ad)) /* 8 */ static int run_bmap_short(struct file_entry *fe, uint32_t offset, uint32_t *out_sector, uint32_t *out_max, int *out_oob) { void *icb; uint32_t icblen = 0; int ad_offset, ad_num = 0; *out_oob = 0; /* ---- VERBATIM kernel loop: sys/vfs/udf/udf_vnops.c:1101-1111 ---- */ do { offset -= icblen; ad_offset = SHORT_AD_SIZE * ad_num; if (ad_offset > (int)fe->l_ad) { /* line 1104 — THE BUG: > should be >= */ return (EINVAL); } /* detect OOB: are we about to read past the live AD area? */ if (ad_offset + SHORT_AD_SIZE > (int)fe->l_ad) *out_oob = 1; /* GETICB below dereferences past l_ad */ icb = GETICB(long_ad, fe, fe->l_ea + ad_offset); icblen = GETICBLEN(short_ad, icb); ad_num++; } while (offset >= icblen); /* ---- end verbatim ---- */ *out_sector = ((struct short_ad *)icb)->pos; *out_max = icblen; return (0); } static int udf_oob_modevent(module_t mod, int type, void *data) { struct file_entry *fe; uint32_t sector = 0, max_size = 0; uint32_t offset = 2048; int rc, oob = 0; int ret = 0; switch (type) { case MOD_LOAD: /* * Allocate exactly one short_ad worth of AD area, plus a guard * short_ad filled with a sentinel. l_ea=0 so data[0] is the * start of the AD area. */ fe = kmalloc(UDF_FENTRY_SIZE + SHORT_AD_SIZE + SHORT_AD_SIZE, M_UDFFENTRY, M_WAITOK | M_ZERO); fe->l_ea = 0; fe->l_ad = SHORT_AD_SIZE; /* 8 bytes = one short_ad */ /* the one valid short_ad: covers 2048 bytes at sector 5 */ ((struct short_ad *)&fe->data[0])->len = 2048; ((struct short_ad *)&fe->data[0])->pos = 5; /* guard: the next 8 bytes (which the buggy loop reads OOB) */ ((struct short_ad *)&fe->data[SHORT_AD_SIZE])->len = 0xDEADBEEFu; ((struct short_ad *)&fe->data[SHORT_AD_SIZE])->pos = 0xCAFEBABEu; rc = run_bmap_short(fe, offset, §or, &max_size, &oob); kprintf("DF-0832: udf_bmap_internal off-by-one in-kernel harness\n"); kprintf(" fentry l_ea=%u l_ad=%u (1 short_ad), offset=%u\n", fe->l_ea, fe->l_ad, offset); kprintf(" loop rc=%d sector=0x%x max_size=0x%x oob_read=%d\n", rc, sector, max_size, oob); if (oob) { kprintf("DF-0832: OOB READ DETECTED at ad_offset==l_ad==%u:\n" " loop dereferenced &fentry->data[l_ea+l_ad] = ONE PAST the\n" " allocation-descriptor area. On a real fentry this is an 8-byte\n" " (short_ad) / 16-byte (long_ad) heap OOB read.\n", fe->l_ad); if (sector == 0xCAFEBABE && max_size == 0xDEADBEEF) kprintf(" CONFIRMED: returned values are the guard sentinel\n" " (0x%08x / 0x%08x).\n", sector, max_size); } else { kprintf("DF-0832: no OOB (bound is tight: >= fired at ad_offset==l_ad).\n"); } kfree(fe, M_UDFFENTRY); break; case MOD_UNLOAD: break; default: ret = EOPNOTSUPP; break; } return (ret); } static moduledata_t udf_oob_mod = { "udf_oob", udf_oob_modevent, NULL }; DECLARE_MODULE(udf_oob, udf_oob_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_VERSION(udf_oob, 1); |