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

2. **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.
