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

dc_submit_i2c indexes dc->links[] with an unvalidated, wrong-domain index (DDC line number from VBIOS): NULL/wild deref panic and silent wrong-connector DDC routing

Summary

dc->links[link_index] is used with no check against link_count, and the only caller passes ddc_pin->hw_info.ddc_channel - an enum gpio_ddc_line DDC line number sourced from the VBIOS object table, NOT a link index. Slots >= link_count in the kzalloc'd links[12] are NULL -> panic on the first DDC/EDID transaction; -1 gives a far-OOB pointer; mismatched-but-valid values route DDC to the wrong physical connector. Upstream later fixed the caller to pass link_index - this tree retains the pre-fix form. Bounds-check + caller-domain fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4216 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 1.6 KB ↓ raw
env.txt β€” 992 B view raw
fix.diff β€” 562 B view raw
manifest.json β€” 798 B view raw
verdict.json β€” 2.0 KB view raw

DF-4216 β€” dc_submit_i2c indexes dc->links[] with an unvalidated, wrong-domain index (DDC line number): NULL/wild deref and wrong-connector routing

Root cause

bool dc_submit_i2c(struct dc *dc, uint32_t link_index, struct i2c_command *cmd)
{
    struct dc_link *link = dc->links[link_index];   /* 1742: NO check */
    struct ddc_service *ddc = link->ddc;             /* 1743: deref    */
    return dce_i2c_submit_command(dc->res_pool, ddc->ddc_pin, cmd);
}

link_index is never validated against dc->link_count (array is links[MAX_PIPES*2] = 12, kzalloc'd β€” slots β‰₯ link_count are NULL, dc.h:287, dc.c:731). The only in-tree caller passes

    dc_submit_i2c(ddc_service->ctx->dc,
                  ddc_service->ddc_pin->hw_info.ddc_channel,   /* !!! */
                  &cmd)

hw_info.ddc_channel is i2c_info.i2c_line β€” a enum gpio_ddc_line DDC line number from the VBIOS connector object table (dc_link_ddc.c:269, gpio_types.h:206-220: GPIO_DDC_LINE_UNKNOWN = -1, DDC1..DDC6 = 0..5, DDC_VGA = 6, VIP/I2C pad = 7), not a link index (link->link_index). Upstream Linux later fixed exactly this β€” current amdgpu passes ddc_service->link->link_index.

Impact

  1. GPIO_DDC_LINE_UNKNOWN (-1) β†’ dc->links[0xffffffff] β†’ read far out of the dc struct β†’ wild dc_link* β†’ link->ddc deref β†’ panic/corruption.
  2. Any DDC line β‰₯ link_count (sparse connector maps: e.g. board exposing 2 connectors whose lines are DDC3/VGA) β†’ links[n] == NULL β†’ NULL deref at 1743 β†’ kernel panic on the first DDC transaction (EDID read during hotplug/detect).
  3. Even when in range and non-NULL, line N β‰  link N in general β†’ DDC/I2C transactions are routed to the wrong physical connector (EDID reads from the wrong monitor; functional correctness / info integrity).

Verification status

untested β€” hardware-gated (no AMD GPU on the guest, env.txt). Source certainty for the missing validation and the domain mismatch; real-world trigger depends on board/VBIOS wiring.

Fix

fix.diff (defensive, in-file): bounds-check link_index against dc->link_count. Real fix belongs in amdgpu_dm.c (pass ddc_service->link->link_index), noted for the upstream report.

VERDICT.md
↓ download raw

VERDICT β€” DF-4216

status: untested (hardware-gated) Β· impact: NULL/wild deref panic on DDC access; wrong-connector I2C routing Β· confidence: certain (source) / likely (trigger)

No AMD GPU on the audit guest (env.txt), so dc_submit_i2c cannot run.

Source proof: - dc.c:1742 dc->links[link_index] with no comparison against dc->link_count; array size is 12 (dc.h:287 links[MAX_PIPES*2], MAX_PIPES=6, hw_shared.h:37), kzalloc'd so unused slots are NULL. - Caller amdgpu_dm.c:4008-4010 passes ddc_pin->hw_info.ddc_channel, assigned from i2c_info.i2c_line (dc_link_ddc.c:269) β€” enum gpio_ddc_line (gpio_types.h:206-220), including UNKNOWN = -1 and values up to 7 β€” an unrelated index domain (DDC line β‰  link index). - Outcomes: -1 β†’ far OOB pointer read then deref; line β‰₯ link_count β†’ NULL->ddc panic (dc.c:1743); mismatched but non-NULL β†’ silent wrong-connector DDC routing. - Confirms upstream divergence: Linux amdgpu_dm later switched to ddc_service->link->link_index in amdgpu_dm_i2c_xfer(); this tree kept the pre-fix form.

Low severity (board/VBIOS-dependent), high code certainty. In-file defensive fix + recommended caller fix documented; not runtime-validated (no hardware) β†’ fix_status: not_testable.

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

diff authored and git-apply-verified against the read-only sys/ tree; runtime fix validation impossible without the triggering hardware on the QEMU guest

findings/poc/DF-4216/fix.diff (git apply --check passed)
↓ fix.diffper-fix-DF-4216

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4216/VERDICT.md, findings/poc/DF-4216/env.txt (guest pciconf: no triggering hardware), findings/poc/DF-4216/fix.diff

PoC changes

no runnable PoC: hardware-gated path absent from the audit guest (env.txt)

Verified recommended fix

bounds-check link_index in dc_submit_i2c and pass ddc_service->link->link_index from amdgpu_dm

Verdict

dc_submit_i2c (dc.c:1742) indexes dc->links[link_index] with no validation against dc->link_count, and the only caller (amdgpu_dm.c:4008-4010) passes ddc_pin->hw_info.ddc_channel β€” a gpio_ddc_line DDC line number (gpio_types.h:206-220, UNKNOWN=-1..7) from the VBIOS object table, not a link index. -1 gives a far-OOB pointer read then deref; line>=link_count gives NULL->ddc panic on the first DDC/EDID transaction; otherwise DDC traffic is silently routed to the wrong connector. Upstream Linux fixed the caller to pass ddc_service->link->link_index.