β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2068

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2068 Β· 7 files
FileTypeDescriptionSize
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
README.md readme Original latent-primitive description
↓ download 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 */
VERDICT.md verdict Full source-trace narrative + fix validation
↓ download raw

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:

Three vector.c call sites pass attacker-influenceable uint32_t operands into that macro:

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)
  • 0 error: lines in the full 35,696-line build log
  • Modules (including amdgpu.ko, which contains vector.o) compile with -Wall ... -Wno-pointer-sign -Werror by default; the patched vector.c TU compiled clean and linked into amdgpu.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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: combined kernel build rc=0 -Werror

VALIDATED: combined kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

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).