Fake-EDID record: kmalloc(128) with memcpy of only ucFakeEDIDLength bytes leaves an uninitialized heap tail that (a) drives an OOB multi-block walk in drm_edid_is_valid via uninit extensions byte and (b) on a ~1/256 checksum pass is exposed to unprivileged users via the EDID ioctl (kernel heap info leak)
Summary
The EDID buffer is kmalloc(max(128,len)) but only len bytes are copied. For len<128 the tail is uninitialized heap: drm_edid_is_valid reads the uninit extensions byte and walks up to 255 extra 128-byte blocks past the allocation; if the block-0 checksum passes (~1/256), the tainted 128-byte EDID is cached and copied wholesale to userspace via GETCONNECTOR - probabilistic kernel heap disclosure (<=127 bytes) to any local user. kzalloc fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4105 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| verdict.json | β | 2.2 KB | view raw | |
| fix.diff | β | 539 B | view raw |
DF-4105 β amdgpu atombios_encoders: fake-EDID record leaves uninitialized kmalloc tail that can be read back by unprivileged users (CWE-457), plus uninit extensions byte drives an OOB walk in drm_edid_is_valid
File: sys/dev/drm/amd/amdgpu/atombios_encoders.c (pass 2)
Class: uninitialized kernel heap memory disclosure (CWE-457/908) + OOB read
Hardware gate: AMD GPU, LCD encoder with a fake-EDID patch record in VBIOS.
Source-level certainty.
Related: DF-1380 (same record loop β bounds of the walk and the OOB
memcpy source; this finding is the uninitialized tail of the destination
β different read source, different fix). radeon twin: DF-3744 mentions the
leak conditionally; here it is isolated with the exact gate.
The defect
atombios_encoders.c:2167-2183:
case LCD_FAKE_EDID_PATCH_RECORD_TYPE:
fake_edid_record = (ATOM_FAKE_EDID_PATCH_RECORD *)record;
if (fake_edid_record->ucFakeEDIDLength) { /* u8, 1..255 */
struct edid *edid;
int edid_size = max((int)EDID_LENGTH, (int)fake_edid_record->ucFakeEDIDLength); /* >= 128 */
edid = kmalloc(edid_size, M_DRM, GFP_KERNEL); /* NOT zeroed */
if (edid) {
memcpy((u8 *)edid, (u8 *)&fake_edid_record->ucFakeEDIDString[0],
fake_edid_record->ucFakeEDIDLength); /* only `len` bytes */
if (drm_edid_is_valid(edid)) {
adev->mode_info.bios_hardcoded_edid = edid;
adev->mode_info.bios_hardcoded_edid_size = edid_size;
When 1 <= ucFakeEDIDLength < 128, bytes [len, 128) of the 128-byte
kmalloc chunk are uninitialized heap. Two consequences:
drm_edid_is_valid(drm_edid.c:1484-1497) loopsfor (i = 0; i <= edid->extensions; i++) drm_edid_block_valid(raw + i*128, ...).edid->extensionsis byte 126 β uninitialized when len < 127. A garbage non-zero value makes the loop read up to 255 additional 128-byte blocks past the 128-byte allocation (OOB heap read; each block is fully read before its checksum rejects it).- If the garbage happens to pass the block-0 checksum (~1/256 per boot
for a random tail), the EDID is cached with the uninit tail included,
and
amdgpu_connector_get_edid(amdgpu_connectors.c:269-274) copies the fullbios_hardcoded_edid_size(128) bytes into the connector's EDID, which any unprivileged user can read back withDRM_IOCTL_MODE_GETCONNECTOR(hw.backlight EDID property) β kernel heap info leak of up to 127-len bytes.
Build / Run
Hardware-gated; audit guest has no AMD GPU. See VERDICT.md.
Fix
Zero the allocation (kzalloc) β one line. See fix.diff.
DF-4105 VERDICT
Status: untested (hardware-gated β no AMD GPU on the audit guest; source-level certainty certain) Impact: conditional kernel-heap info leak to unprivileged userspace (β€127 bytes, ~1/256 per boot) + up-to-32KB OOB heap read probe during validation.
Trace
amdgpu_atombios_encoder_get_lcd_inforecord walk (atombios_encoders.c:2156) reaches aLCD_FAKE_EDID_PATCH_RECORD_TYPErecord (2167).edid = kmalloc(max(128, len))at 2173 β plain kmalloc, no zeroing.memcpy(edid, ..., len)at 2175-2176 initializes onlylenbytes.drm_edid_is_valid(edid)at 2178 (drm_edid.c:1484): readsedid->extensions= byte 126 (uninit when len β€ 126) and walksraw + i*128for i in [0, extensions] β each block fully read (header + checksum) before rejection β OOB read past the chunk when garbage extensions != 0 (likely for random heap: ~99.6%).- On block-0 checksum pass (~1/256 with random tail): cached as
bios_hardcoded_edid(2179-2180). - Userspace readback:
amdgpu_connector_get_edid(amdgpu_connectors.c:269-274) duplicates the full 128-byte buffer (tail included) into the connector EDID β returned via the standard GETCONNECTOR ioctl / debugfs EDID node, readable by any local user.
Reachability gate: crafted VBIOS with a fake-EDID record whose declared length is < 128 (or β€ 126 for the extensions variant). Same threat model as DF-1380 (root-flashed VBIOS / malicious option ROM) β hence Low severity, consistent with DF-1380's rating.
Not DF-1380: DF-1380's fix (bound the record pointer vs bios_end before the walk and before the memcpy) does NOT touch this β the leak source is the destination buffer's uninitialized tail, fixed by kzalloc.
Why untested
Guest has no AMD GPU (bochs 1234:1111 only); the record walk never executes. Source trace is line-exact.
Fix
edid = kzalloc(edid_size, M_DRM, GFP_KERNEL); β zero-initialize so the
tail cannot leak heap contents and edid->extensions is deterministic
(0 β single-block validation only).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableOne-line kzalloc fix removes both the uninit-tail leak and the uninit extensions-byte walk; not buildable/testable on guest (no AMD GPU).
findings/poc/DF-4105/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md: the three code excerpts (atombios_encoders.c:2167-2183, drm_edid.c:1484-1497, amdgpu_connectors.c:269-274) and the 1/256 checksum-gate analysis.
PoC changes
No runnable PoC (hardware absent); pack is the source trace + one-line kzalloc fix.
Verified recommended fix
kzalloc instead of kmalloc for the EDID buffer (fix.diff).
Verdict
Source-level certain: the fake-EDID record handler kmalloc()s max(128,len) bytes but memcpy()s only ucFakeEDIDLength of them (atombios_encoders.c:2173-2176); for len<128 the tail is uninitialized heap. drm_edid_is_valid (drm_edid.c:1484-1497) then reads the uninit byte126 (extensions) and walks up to 255 extra 128-byte blocks past the chunk (OOB heap read), and on a ~1/256 checksum pass the tainted 128-byte EDID is cached as bios_hardcoded_edid and copied wholesale to userspace via amdgpu_connector_get_edid (amdgpu_connectors.c:269-274) + GETCONNECTOR ioctl - kernel heap info leak. Distinct from DF-1380 (walk/memcpy source bounds; this is the destination's uninit tail, fixed by kzalloc). Hardware absent on guest.
No comments yet.