Unchecked capacity*struct_size multiplication can overflow in construct/presized/reserve (latent heap OOB)
Summary
dal_vector_construct (vector.c:43 kcalloc(capacity,struct_size) which is kzalloc(capacity*struct_size) with NO overflow check per linux/slab.h:44), dal_vector_presized_costruct (vector.c:71 same), dal_vector_reserve (vector.c:293-294 krealloc(capacity*struct_size) hand multiply) -- all uint32_t operands wrap mod 2^32 before promotion to size_t. After wrapped small alloc vector->capacity/count holds large pre-wrap value -> every subsequent indexed write OOB. calc_increased_capacity (198-202) returns old_capacity*2 no overflow guard; if wraps <=capacity reserve no-ops and insert_at writes past capacity. dal_vector_insert_at exported trusts position arg (218) position>=capacity -> OOB insert_address. NOT currently exploitable: sole live caller dc_link_ddc.c bounds count<=32 struct_size compile-time sizeof no path overflows uint32. Filed defense-in-depth because AMD DC continually synced from upstream Linux where vector API has many more callers some taking count from parsed EDID/MST/AUX data; shim kcalloc differs from real Linux kcalloc (array_size/check_mul_overflow). Future caller or position bug -> heap overflow from EDID/MST context processing external monitor data.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2068 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Full source-trace narrative + fix validation | 3.4 KB | β raw |
| README.md | readme | Original latent-primitive description | 1.1 KB | β raw |
| fix.diff | suggested-fix | git-apply-able overflow guards in 3 vector.c functions | 1.4 KB | view raw |
| build.sh | reproduce | Combined kernel build script | 755 B | view raw |
| run.sh | reproduce | Source-only confirmation; no runtime PoC | 543 B | view raw |
| fix_build.log | build-log | Full untrimmed combined kernel build (rc=0, 0 errors) | 5.6 MB | β download |
| env.txt | environment | uname, kern.version, cc version | 501 B | view raw |
DF-2068 PoC β Unchecked capacity*struct_size overflow (latent / defense-in-depth)
Status: VERIFIED (source-only) + FIX VALIDATED
The overflow pattern is real in three dal_vector_* functions
(vector.c:43, :71, :293-294); kcalloc is kzalloc(n*size) with
no overflow check (linux/slab.h:44). Currently latent β the lone
in-tree caller (dc_link_ddc.c) bounds count<=32 with a compile-time
sizeof, so no live caller overflows uint32_t. Filed as
defense-in-depth.
See VERDICT.md for the full source-trace and the validation of
fix.diff (which adds capacity > 0xffffffffu / struct_size guards).
Reproduce
./build.sh # rebuilds the patched kernel (rc=0 with -Werror) ./run.sh # source-only confirmation; no runtime PoC (latent)
Latent primitive
dal_vector_create(ctx, 0x40000000u, 0x10u);
/* capacity*struct_size = 2^30 * 16 = 2^34 wraps to 0 in uint32 */
/* kcalloc allocates ~0 bytes; vector->capacity = 2^30 */
dal_vector_append(vec, &elem);
/* writes 16 bytes past 0-byte slab allocation -> heap corruption */
DF-2068 β REPRODUCED (source-confirmed, latent) + FIX VALIDATED
Verdict
REPRODUCED via source-only trace. The unchecked capacity * struct_size
multiply pattern is real in three dal_vector_* entry points; the finding's
classification as defense-in-depth / latent is also confirmed (no in-tree
caller currently supplies a value that overflows uint32_t).
Mechanism (path:line)
The DRM linux-compat shim defines kcalloc as a raw multiply with no
overflow guard:
sys/dev/drm/include/linux/slab.h:44c #define kcalloc(n, size, flags) kzalloc((n) * (size), flags)
Three vector.c call sites pass attacker-influenceable uint32_t operands
into that macro:
sys/dev/drm/amd/display/dc/basics/vector.c:43dal_vector_construct:kcalloc(capacity, struct_size, GFP_KERNEL)thenvector->capacity = capacityβ if the multiply wraps,containeris tiny butcapacityis huge, so subsequentdal_vector_deposit()/dal_vector_append()writes out of bounds.sys/dev/drm/amd/display/dc/basics/vector.c:71dal_vector_presized_costruct: same pattern withcount.sys/dev/drm/amd/display/dc/basics/vector.c:293-294dal_vector_reserve: hand-rolledkrealloc(..., capacity * struct_size)with the same wrap.
If a caller ever supplies capacity = 0x40000000, struct_size = 16 the
product 2^34 truncates to 0 in uint32_t, kzalloc(0) returns a
tiny slab object, and vector->capacity = 0x40000000 so the next
deposit() writes 16 bytes far past the slab β a classic heap overflow.
Reachability / impact ceiling
Latent. The single in-tree caller of dal_vector_construct is
dc_link_ddc.c (via aux engine), which uses a small compile-time
sizeof(struct i2c_reg_helper) and count <= 32; nothing in the tree
feeds parsed (EDID/MST) data into capacity. The primitive is therefore
a real defense-in-depth gap that would become a heap overflow the moment
a future caller routes untrusted data through dal_vector_create.
Fix
fix.diff adds if (capacity > 0xffffffffu / struct_size) { ... return false; }
guards in all three functions, matching the existing
if (!struct_size || !capacity) early-return pattern. Uses the literal
0xffffffffu (no <stdint.h> in kernel headers, so UINT32_MAX is
unavailable; the literal is the kernel idiom).
Supersedes the finding markdown's recommendation (overflow guard at every multiply).
Phase-8 build validation
Combined kernel + modules build of all 5 fixes (DF-2068 / DF-2069 /
DF-2070 / DF-2071 / DF-2072) on DragonFly 6.5-DEVELOPMENT #0 baseline
with make -j6 nativekernel KERNCONF=X86_64_GENERIC:
=== NK_DONE rc=0 ===(build completed 2026-07-25 11:31:20 UTC)0error:lines in the full 35,696-line build log- Modules (including amdgpu.ko, which contains
vector.o) compile with-Wall ... -Wno-pointer-sign -Werrorby default; the patchedvector.cTU compiled clean and linked intoamdgpu.ko.
The default DragonFly kernel/module build is a -Werror build
(bsd.sys.mk enables -Werror whenever the compiler is gcc80, which is
what the guest uses); -Wno-pointer-sign suppresses the pre-existing
linux-compat shim noise. See fix_build.log for the full untrimmed
output and env.txt for the guest environment.
Reproduce
./build.sh # rebuilds the patched kernel (rc=0 with -Werror) ./run.sh # source-only confirmation; no runtime PoC (latent)
Fix verification
fixedVALIDATED: combined kernel build rc=0 -Werror
VALIDATED: combined kernel build rc=0 -Werror
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
Source-confirmed: kcalloc(n,size) overflow in dal_vector_construct/presized/reserve. Latent (lone caller bounds count<=32).
Verified recommended fix
Source-confirmed: kcalloc(n,size) overflow in dal_vector_construct/presized/reserve. Latent (lone caller bounds count<=32).
Verdict
Source-confirmed: kcalloc(n,size) overflow in dal_vector_construct/presized/reserve. Latent (lone caller bounds count<=32).
No comments yet.