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

acquire_i2c_hw_engine bounds the signed gpio_ddc_line index only above (line < GPIO_DDC_LINE_COUNT): GPIO_DDC_LINE_UNKNOWN(-1) passes and reads i2c_hw_engines[-1] β€” dce110 twin of DF-2089 (dce80)

Summary

Signed enum with UNKNOWN=-1 passes the upper-only check. Traced benign today only by a struct-layout accident ([-1] aliases an always-NULL sw_engines slot); fragile under layout changes. Crafted-VBIOS reachability on real DCE11 hw. Enum MIN/MAX lower-bound check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5034 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 3.4 KB ↓ raw
analysis.sh β€” 847 B view raw
fix.diff β€” 458 B view raw
verdict.json β€” 2.8 KB view raw
manifest.json β€” 899 B view raw

DF-5034 β€” i2caux_dce110 acquire_i2c_hw_engine: missing lower-bound check on signed gpio_ddc_line allows negative-index array read (dce110 twin of DF-2089)

What the code does

acquire_i2c_hw_engine() derives the engine-slot index from the DDC object:

if (ddc->hw_info.hw_supported) {
        enum gpio_ddc_line line = dal_ddc_get_line(ddc);
        if (line < GPIO_DDC_LINE_COUNT)                       /* only UPPER bound */
                engine = i2caux->i2c_hw_engines[line];        /* i2caux_dce110.c:98-99 */
}

enum gpio_ddc_line is SIGNED and its first member is GPIO_DDC_LINE_UNKNOWN = (-1) (sys/dev/drm/amd/display/include/gpio_types.h:206-220). The enum API explicitly ships GPIO_DDC_LINE_MIN/GPIO_DDC_LINE_MAX for bounds checks, but this site (and its dce80 twin, DF-2089) only compares against GPIO_DDC_LINE_COUNT. A -1 passes (-1 < 8) and reads i2c_hw_engines[-1].

Why it is benign today (and why it is filed anyway)

struct i2caux layout (i2caux.h:55-98): ctx, funcs, i2c_sw_engines[8], i2c_hw_engines[8], aux_engines[8], … so i2c_hw_engines[-1] aliases i2c_sw_engines[7]. In the whole dce110 constructor family (dce100/dce110/dce112/dce120/dcn10 all funnel through dal_i2caux_dce110_construct, i2caux_dce110.c:202-295) the SW-engine branch is dead (use_i2c_sw_engine = false, i2caux_dce110.c:213) and only line slots DDC1..DDC6 (indices 0..5) are ever written, so i2c_sw_engines[7] stays NULL and the function returns NULL. No OOB past the struct occurs.

Reachability of line == -1 additionally requires a ddc whose hw_info.hw_supported is true (a VBIOS-derived flag: i2c_hw_assist, core/dc_link_ddc.c:270) while dal_ddc_get_line() — which returns dal_gpio_get_enum(ddc->pin_data) (gpio/gpio_service.c:566-570), an enum produced by the register-offset→id translation (gpio_service.c:418) — yields UNKNOWN. No such path was found on a well-formed ASIC/BIOS; a crafted VBIOS on real AMD hardware is the only conceivable trigger, and it still lands on NULL. Filed as the dce110 bookkeeping twin of DF-2089 (dce80, identical shape, Info) so the family record is complete and the next refactor does not have to re-derive this.

Fix (one line)

-       if (line < GPIO_DDC_LINE_COUNT)
+       if (line >= GPIO_DDC_LINE_MIN && line < GPIO_DDC_LINE_COUNT)
            engine = i2caux->i2c_hw_engines[line];

Reproduce / evidence

No PoC run: the finding is an Info hardening note gated on AMD display hardware (DCE11 ASIC) that the QEMU test guest does not have (guest has only a virtio GPU; no radeon/amdgpu KLD). Run ./analysis.sh to print the cited source lines. See VERDICT.md.

VERDICT.md
↓ download raw

DF-5034 β€” VERDICT

status: untested (PoC intentionally not run β€” Info hardening finding, hardware-gated; no AMD ASIC in the test guest) reproduced: 0, impact: none, confidence: certain (about the code shape)

Narrative

Pass-2 audit of sys/dev/drm/amd/display/dc/i2caux/dce110/i2caux_dce110.c found that acquire_i2c_hw_engine() validates the DDC-line index only against the UPPER bound:

  • i2caux_dce110.c:95-100 if (ddc->hw_info.hw_supported) { enum gpio_ddc_line line = dal_ddc_get_line(ddc); if (line < GPIO_DDC_LINE_COUNT) engine = i2caux->i2c_hw_engines[line]; }

enum gpio_ddc_line is signed with GPIO_DDC_LINE_UNKNOWN = (-1) (include/gpio_types.h:207), so a -1 passes the check and indexes the array one element low.

Why this is NOT a demonstrable memory-safety bug here (traced, not guessed):

  1. i2c_hw_engines[-1] aliases i2c_sw_engines[7] per the struct layout in i2caux/i2caux.h:67-71 (sw array immediately precedes hw array).
  2. i2c_sw_engines[7] is never assigned in the dce110 constructor family: use_i2c_sw_engine is hardcoded false (i2caux_dce110.c:213), and even the dce80 variant only fills hw lines DDC1..DDC6 = indices 0..5 (i2caux_dce80.c:222-245, loop bounded by ARRAY_SIZE(hw_ddc_lines)). The slot is therefore NULL and the function returns NULL β€” benign.
  3. Reaching line == -1 at all needs a hw_supported ddc (flag comes from VBIOS i2c_hw_assist, core/dc_link_ddc.c:270) whose gpio pin enum (dal_gpio_get_enum via the offset→id translation, gpio/gpio_service.c:418,566-570) evaluates to UNKNOWN. No in-tree creator does that; it would take a crafted VBIOS on real DCE11 hardware.

This is the dce110 twin of already-filed DF-2089 (dce80, i2caux_dce80.c:122-127, Info). Filed for family completeness: the enum provides GPIO_DDC_LINE_MIN (gpio_types.h:218) precisely for this check, and the current "benign by struct-layout accident" property is fragile under any future struct/layout or engine-population change.

Exploit chain

None. No attacker-controllable path to a non-NULL outcome was found; worst case remains a NULL deref-free early return.

Why untested (not not_reproduced)

The test guest (DragonFly 6.5-DEVELOPMENT, QEMU/KVM) has no AMD display controller (pciconf shows only virtio GPU 1af4:1100) and no drm KLD loaded; the DCE11 i2caux constructor/acquire path cannot execute there. The finding is Info hardening; per the audit contract, Low/Info hardening findings do not get a PoC phase. analysis.sh mechanically prints every cited line for reviewers.

Fix validation

fix.diff adds the GPIO_DDC_LINE_MIN guard. Not built/booted (nothing to observe without DCE11 hardware; the change is a pure index guard).

kernel_refs: - sys/dev/drm/amd/display/dc/i2caux/dce110/i2caux_dce110.c:92-100 - sys/dev/drm/amd/display/include/gpio_types.h:206-220 - sys/dev/drm/amd/display/dc/gpio/gpio_service.c:566-570 - sys/dev/drm/amd/display/dc/i2caux/i2caux.h:55-98 - twin: sys/dev/drm/amd/display/dc/i2caux/dce80/i2caux_dce80.c:122-127 (DF-2089)

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_testable
baseline no→ patch + rebuild →patched clean

n/a β€” finding not reproduced (no AMD ASIC in the test guest); fix is a pure index guard, reviewed against the enum definition only.

fix.diff (GPIO_DDC_LINE_MIN guard, mirrors the DF-2089 fix shape).
↓ fix.diffper-fix-DF-5034

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md and VERDICT.md (full trace); run ./analysis.sh to print every cited line: i2caux_dce110.c:92-112, gpio_types.h:206-220 (UNKNOWN=-1, GPIO_DDC_LINE_MIN exists at :218), i2caux.h:55-98 (array layout), gpio_service.c:566-570 (dal_ddc_get_line), dce80 twin i2caux_dce80.c:122-127.

PoC changes

No PoC written β€” finding is an index-guard hardening note; evidence is a mechanical source-print script (analysis.sh) instead.

Verified recommended fix

Add the enum-provided lower bound: if (line >= GPIO_DDC_LINE_MIN && line < GPIO_DDC_LINE_COUNT) β€” see fix.diff.

Verdict

acquire_i2c_hw_engine() bounds the DDC-line index only above (line < GPIO_DDC_LINE_COUNT, i2caux_dce110.c:98) although enum gpio_ddc_line is signed with GPIO_DDC_LINE_UNKNOWN = -1 (gpio_types.h:207); a -1 reads i2c_hw_engines[-1], which struct-layout-wise aliases i2c_sw_engines[7] (i2caux.h:67-71) β€” a slot that is never populated in the dce110 constructor family (use_i2c_sw_engine hardcoded false at i2caux_dce110.c:213; hw lines fill only indices 0..5), so the read is NULL and the function returns NULL. Benign today, fragile by accident, unreachable without a crafted VBIOS on real DCE11 hardware. Filed as the dce110 twin of DF-2089 (dce80). No PoC run: Info hardening finding, and the QEMU guest has no AMD display controller (virtio GPU only, no drm KLD).