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

Pin-busy bitmap indexed by unbounded caller-supplied en (no number_of_pins bound) and NULL create_generic/sync/gsl creator pointers invoked unchecked

Summary

is/set_pin_busy index the busyness bitmap with a raw uint32 en never bounded against number_of_pins; all four ASIC factory tables set create_generic/sync/gsl=NULL while advertising pin counts, called unchecked. No current caller reaches either arm (translate tables clamp en) - defense-in-depth: a future unvalidated en makes set_pin_busy a small-offset OOB heap write. Bound-at-entry fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4788 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
env.txt β€” 920 B view raw
fix.diff β€” 500 B view raw
manifest.json β€” 762 B view raw
verdict.json β€” 2.4 KB view raw

DF-4788 β€” gpio_service pin-busy bitmap indexed by unbounded en; NULL creator pointers called unchecked

What (two hardening gaps in dal_gpio_service_open / pin-busy helpers)

  1. Unbounded en indexing. gpio_service.c:190-222: c const uint32_t *slot = service->busyness[id] + (en / bits_per_uint); /* is_pin_busy */ service->busyness[id][en / bits_per_uint] |= (1 << (en % bits_per_uint)); /* set_pin_busy */ service->busyness[id][en / bits_per_uint] &= ~(1 << (en % bits_per_uint)); /* set_pin_free */ The bitmap is sized from factory.number_of_pins[id] (gpio_service.c:89-94, e.g. 8 for DDC_DATA, 6 for HPD, per hw_factory_dce110.c:168-171). Nothing in dal_gpio_service_open (gpio_service.c:233-241 only checks busyness[id] != NULL) enforces en < number_of_pins[id]. An out-of-range en would be an OOB heap read+write past the kcalloc'd bitmap.

Today every caller passes en obtained from the hw-translate mask switches (hw_translate_dce110.c:41-101, bounded enum constants) or compile-time enums, so the OOB is NOT currently reachable β€” dal_hw_ddc_create/dal_hw_hpd_create additionally range-check en (hw_ddc.c:219, hw_hpd.c:149) β€” but the service layer itself trusts a raw uint32_t it does not own.

  1. NULL creator function pointers. All four shipped ASIC factory tables set .create_generic = .create_sync = .create_gsl = NULL (hw_factory_dce110.c:148-151, hw_factory_dce80.c:152-155, hw_factory_dce120.c:169-172, hw_factory_dcn10.c:164-167) while still advertising number_of_pins[GENERIC]=7 / [SYNC]=2 / [GSL]=4. dal_gpio_service_open (gpio_service.c:254-270) calls these pointers with no NULL check β†’ NULL function-pointer call panic. Tree-wide grep shows no caller ever opens GENERIC/SYNC/GSL pins (only DDC + HPD are opened via dal_ddc_open / HPD IRQ registration), so this arm is latent β€” but number_of_pins advertising pins the factory cannot create is a trap for any future caller.

Impact

Defense-in-depth only (no demonstrated reachable path). Filed as Info.

Fix

Bound en against number_of_pins[id] at the top of dal_gpio_service_open and reject NULL create_* pointers. The en bound + creator guards are included in findings/poc/DF-4787/fix.diff (same function); the minimal en hunk is repeated here as fix.diff.

Verdict

untested β€” hardening finding, no trigger path exists on current callers; guest has no AMD GPU in any case.

VERDICT.md
↓ download raw

DF-4788 VERDICT

  • Status: untested (hardening / latent, no reachable path)
  • Confidence: likely
  • Severity filed: Info

Analysis

Unbounded en bitmap indexing (CWE-129 / CWE-787 latent)

  • Sizing: gpio_service.c:88-118 β€” kcalloc((number_of_pins[id] + 31) / 32, 4) stored in service->busyness[id].
  • Use: gpio_service.c:190-222 β€” is_pin_busy / set_pin_busy / set_pin_free index busyness[id][en/32] with a caller-supplied uint32_t en; no comparison against number_of_pins[id] anywhere between dal_gpio_service_open (:224) and the bitmap touch. set_pin_busy is called at :287 with the raw en.
  • Bound sources today (why unreachable):
  • DDC: dal_gpio_create_ddc (gpio_service.c:418) gets en from translate.funcs->offset_to_id() whose mask switches only emit GPIO_DDC_LINE_DDC1..I2C_PAD (dce110/hw_translate_dce110.c DDC case); dal_hw_ddc_create re-validates (hw_ddc.c:219-222) before allocating.
  • HPD: dal_gpio_service_create_irq (gpio_service.c:151) β€” same translate mechanism, masks emit GPIO_HPD_1..6 only; dal_hw_hpd_create re-validates (hw_hpd.c:144-152).
  • dal_gpio_create_irq rejects every id except HPD/GPIO_PAD (gpio_service.c:373-381) and GPIO_PAD pins cannot open at all (no case in dal_gpio_service_open).
  • Consequence if a future caller passes raw en: OOB heap write (set_pin_busy) / read (is_pin_busy) β€” the bitmap is a small standalone kmalloc, so an overflow lands in adjacent slab objects. That is why the missing bound is worth fixing even though it is presently dead.

NULL creator pointers

  • hw_factory_dce110.c:144-154, hw_factory_dce80.c:150-156, hw_factory_dce120.c:167-173, hw_factory_dcn10.c:162-169: .create_generic, .create_sync, .create_gsl are NULL; .define_*_registers present.
  • gpio_service.c:254-270 invokes them without a NULL check (NULL text pointer call β†’ panic at address 0).
  • Tree-wide rg 'GPIO_ID_GENERIC|GPIO_ID_SYNC|GPIO_ID_GSL' shows users only inside the gpio directory itself (translate tables, dal_gpio_get_sync_source which never opens); no display component opens those ids β†’ latent.

Why untested

No AMD GPU in the audit QEMU guest (env.txt), and even on hardware there is no caller that reaches the defective arms β€” this is a defense-in-depth finding by construction.

Add en >= service->factory.number_of_pins[id] β†’ GPIO_RESULT_INVALID_DATA at the top of dal_gpio_service_open, and NULL-check the create_* pointers (already included in DF-4787/fix.diff).

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

one-line bounds check; no behavioral change for any current caller (all pass in-range enums); cannot be boot-validated without AMD hardware

findings/poc/DF-4788/fix.diff
↓ fix.diffper-fix-DF-4788

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md, VERDICT.md (bounds trace), env.txt (guest pci inventory), fix.diff (minimal en-bound hunk)

PoC changes

no PoC produced β€” no reachable trigger path exists in current callers; hardware-gated besides

Verified recommended fix

Bound en against factory.number_of_pins[id] in dal_gpio_service_open and NULL-check the create_* factory pointers

Verdict

Latent hardening gaps in dal_gpio_service_open: (1) is_pin_busy/set_pin_busy/set_pin_free index the number_of_pins-sized busyness bitmap with a raw caller-supplied uint32_t en that is never bounds-checked in the service layer (gpio_service.c:197-221 vs sizing at :89-94) β€” an out-of-range en would be an OOB heap read/write, but every current caller passes translate-table-bounded enum constants so the path is unreachable today; (2) .create_generic/.create_sync/.create_gsl are NULL on all four shipped ASIC factory tables yet are invoked without NULL checks (gpio_service.c:254-270) β€” no display code ever opens those pin classes, so the NULL call is also latent. Filed as defense-in-depth; not exercisable on the audit guest (no AMD GPU).