dc_add_all_planes_for_stream writes plane_states[MAX_SURFACES=3] with unchecked plane_count (dead API, latent stack overflow)
Summary
dc_add_all_planes_for_stream at dc_resource.c:1564: stack struct dc_validation_set set; plane_states[MAX_SURFACES=3]. :1568 set.plane_count=plane_count (raw caller). :1570-1571 for(i=0;i<plane_count;i++) set.plane_states[i]=plane_states[i]. No check vs MAX_SURFACES. plane_count>3 -> stack overflow (writes past plane_count field, padding, saved registers). DEAD API: zero in-tree callers (confirmed). Latent hazard if future DC consumer wires it without clamping. Fix: check plane_count<=MAX_SURFACES.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1300 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | replica of dc_validation_set+dc_add_all_planes_for_stream with canary fields; plane_count=8 corrupts frame | 6.6 KB | view raw |
| build.sh | build-script | cc -O0 -Wall -o harness harness.c (-O0 required; -O2 may elide OOB as UB) | 500 B | view raw |
| run.sh | run-script | ./harness | 60 B | view raw |
| build.log | build-log | final successful build | 65 B | view raw |
| run.log | run-log | decisive run: canaries CORRUPTED, CONFIRMED | 1.1 KB | view raw |
| fix.diff | suggested-fix | clamp plane_count to MAX_SURFACES before the loop | 673 B | view raw |
| env.txt | environment | uname, cc version | 552 B | view raw |
| VERDICT.md | verdict | full narrative: dead API, mechanism, harness, optimizer nuance, fix | 4.2 KB | β raw |
| README.md | readme | build/run/expected | 1.0 KB | β raw |
| fix_module_proof.txt | fix-build-proof | amdgpu.ko built with fix applied under -Werror, 0 errors, target .o produced | 389 B | view raw |
| fix_module_build.log | fix-build-log | amdgpu module build excerpt: ci_smumgr.o/bios_parser.o/dc_resource.o compiled, amdgpu.ko linked | 32.2 KB | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1300 β dc_add_all_planes_for_stream unchecked plane_count stack overflow (dead API)
Severity: Medium Β· CWE: CWE-787 (Out-of-bounds Write)
File: sys/dev/drm/amd/display/dc/core/dc_resource.c:1564-1571
Build & run (AMD-DC latent bug, DEAD API β no AMD GPU on guest, harness proof)
./build.sh # cc -O0 -Wall -o harness harness.c (-O0 required) ./run.sh # ./harness
Expected output (bug present)
saved_rbp_canary = 0x00007fffffdfd910 *** CORRUPTED *** saved_rip_canary = 0x00007fffffdfd914 *** CORRUPTED *** RESULT: stack buffer overflow CONFIRMED at dc_resource.c:1570-1571
Why -O0
At -O2 gcc's -faggressive-loop-optimizations may silently clamp the loop
to MAX_SURFACES iterations (the OOB writes are UB and get elided). The kernel
builds at -O2 and MAY compile out the bug β but that is fragile and the
correct fix is the explicit bounds check in fix.diff.
Status
Dead public API (0 in-tree callers). Latent stack-overflow hazard for any
future DC consumer. See VERDICT.md.
DF-1300 β dc_add_all_planes_for_stream unchecked plane_count stack overflow (dead API)
Verdict: REPRODUCED (source-level + harness) β latent AMD-DC bug, stack overflow (dead API)
The AMD Display Core (DC) dc_resource.c is part of the amdgpu DRM module,
not in X86_64_GENERIC and no AMD GPU is present on the audit guest.
Moreover dc_add_all_planes_for_stream is a dead public API β zero
in-tree callers (only the definition at dc_resource.c:1557 and the
prototype at dc_stream.h:218). The bug is therefore a latent hazard:
confirmed by source trace and reproduced at the object level, but not
triggerable on this guest. Any future DC consumer that wires this API without
clamping plane_count to MAX_SURFACES gets a stack overflow.
The bug
sys/dev/drm/amd/display/dc/core/dc_resource.c:1557-1574:
bool dc_add_all_planes_for_stream(..., int plane_count, ...)
{
struct dc_validation_set set; /* ON THE STACK */
int i;
set.stream = stream;
set.plane_count = plane_count; /* :1568 raw caller, NO clamp */
for (i = 0; i < plane_count; i++) /* :1570 NO check vs MAX_SURFACES */
set.plane_states[i] = plane_states[i]; /* :1571 the overflow */
...
}
struct dc_validation_set (dc.h:597-601):
#define MAX_SURFACES 3 /* dc.h:43 */
struct dc_validation_set {
struct dc_stream_state *stream; /* off 0 */
struct dc_plane_state *plane_states[MAX_SURFACES]; /* off 8, 3 ptrs */
uint8_t plane_count; /* off 32 (immediately after) */
};
plane_states[3] (the 4th element) aliases the plane_count field;
plane_states[4+] aliases stack padding, the saved frame pointer, and the
return address. A plane_count > MAX_SURFACES is a classic stack buffer
overflow that, depending on the caller-controlled pointer values, can hijack
control flow on return.
Dead-API verification
$ grep -rn "dc_add_all_planes_for_stream" sys/dev/drm/ sys/dev/drm/amd/display/dc/core/dc_resource.c:1557:bool dc_add_all_planes_for_stream( # definition sys/dev/drm/amd/display/dc/dc_stream.h:218:bool dc_add_all_planes_for_stream( # prototype
Zero call sites. The function is exported as public DC API, so an out-of-tree or future in-tree consumer could call it.
Harness proof
harness.c replicates dc_validation_set layout with canary fields after
plane_states[] (standing in for plane_count + saved registers), calls the
replicated function with plane_count=8, and shows the writes past
plane_states[2] corrupt the canaries with attacker-supplied stack pointers.
Decisive output:
MAX_SURFACES=3 sizeof(plane_states)=24 (3 ptrs) calling vulnerable_add_planes(plane_count=8) -- MAX_SURFACES=3 => 5 OOB writes past plane_states[MAX_SURFACES-1] expected BEFORE: plane_count=0 saved_rbp_canary=0xdeadbeefcafebabe saved_rip_canary=0x1122334455667788 AFTER: plane_states[0..2] = 0x7fffffdfd900..08 (in-bounds) plane_count (overwritten by [3]) = 12 saved_rbp_canary = 0x00007fffffdfd910 *** CORRUPTED *** saved_rip_canary = 0x00007fffffdfd914 *** CORRUPTED *** RESULT: stack buffer overflow CONFIRMED at dc_resource.c:1570-1571
-O0 is required: at -O2 gcc's -faggressive-loop-optimizations sees the
plane_states[MAX_SURFACES] array bound and may silently clamp the loop to 3
iterations (declaring the OOB writes UB and eliding them). This optimizer
behavior is itself worth noting β it means the bug may be compiled out of
some kernels, but relying on that is fragile (gcc version/flag dependent) and
the correct fix is the explicit bounds check. The kernel's actual behavior
depends on the gcc version and CFLAGS used to build amdgpu.ko.
Build & run
./build.sh # cc -O0 -Wall -o harness harness.c (-O0 required, see above) ./run.sh # ./harness
Fix
fix.diff clamps plane_count to MAX_SURFACES before the loop:
if (plane_count > MAX_SURFACES)
plane_count = MAX_SURFACES;
This matches the finding proposal ("check plane_count<=MAX_SURFACES") and makes the dead API safe for any future caller. (Clamping rather than returning false preserves the current contract of "add up to MAX_SURFACES planes".)
Fix verification
fixedvalidated
module build rc=0 + harness
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. dc_add_all_planes_for_stream plane_count>MAX_SURFACES -> stack overflow (dead API, 0 callers). amdgpu not in GENERIC.
No comments yet.