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

id_to_offset GPIO_ID_GPIO_PAD branch compares the derived mask against the pin-index bound GPIO_GPIO_PAD_MAX: valid pads 5..30 wrongly rejected; enβ‰₯32 hits the C11 6.5.7 UB shift and is accepted with a bogus mask

Summary

Mask-vs-index type confusion: valid pads rejected, en>=32 accepted via UB shift (on gcc 8.3/x86-64 mask==0 passes). Latent (the API's only caller is dead; GPIO_PAD pins cannot be opened). Index-validate-before-shift fix; unit-proven both directions.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4944 Β· 13 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 1.7 KB ↓ raw
harness_dce80.c β€” 18.9 KB view raw
harness_fixed.c β€” 19.0 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 89 B view raw
build.log β€” 68 B view raw
run.log β€” 2.1 KB view raw
fixrun.log β€” 2.0 KB view raw
env.txt β€” 419 B view raw
fix.diff β€” 3.3 KB view raw
manifest.json β€” 694 B view raw
verdict.json β€” 2.6 KB view raw

DF-4944 β€” hw_translate_dce80.c: id_to_offset GPIO_PAD branch compares the derived MASK against the pin INDEX bound (valid pads 5..30 rejected; enβ‰₯32 hits UB shift and is accepted)

File audited (pass 2): sys/dev/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c Lines: 378-382.

case GPIO_ID_GPIO_PAD:
        info->offset = mmGPIOPAD_A;
        info->mask = (1 << en);
        result = (info->mask <= GPIO_GPIO_PAD_MAX);   /* mask vs index! */
break;

GPIO_GPIO_PAD_MAX is the largest valid pin index (30, gpio_types.h:156), but the code compares the derived mask (1<<en) against it. Two consequences, both reproduced on the DF guest:

What to run / expect

./build.sh && ./run.sh     # harness_dce80.c T4 section (run.log)
id_to_offset(GPIO_PAD,en=5)  = false mask=0x20 (pad 5 is VALID per enum but rejected: 1<<5=32 > 30)
id_to_offset(GPIO_PAD,en=30) = false mask=0x40000000 (pad 30 VALID, rejected)
id_to_offset(GPIO_PAD,en=32) = true mask=0x0 (en=32 is INVALID; UB shift 1<<32 -> ACCEPTED with bogus mask)
id_to_offset(GPIO_PAD,en=64) = true mask=0x0 (invalid, also accepted via UB)
  • Over-restriction: valid pads 5..30 (enum GPIO_GPIO_PAD_5..30) are rejected because their masks (32..2^30) exceed 30. Functional regression: GPIO-pad pins 5..30 could never obtain pin info.
  • UB acceptance: 1 << en with en >= 32 is undefined (C11 6.5.7); on the guest's gcc 8.3/x86-64 the UB manifests as mask == 0, and 0 <= 30 is true β€” invalid pad indices 32/64 are accepted with a degenerate mask. (Other toolchains may yield 1<<0=1; either way an invalid en passes.)

Reachability (why Info severity)

id_to_offset is reachable only via dal_gpio_get_pin_info (gpio_base.c:140-147), which has no callers anywhere in sys/ (dead public API β€” grep: definition + header declaration only). Additionally dal_gpio_service_open has no GPIO_ID_GPIO_PAD case (gpio_service.c:243-274), so GPIO_PAD pins cannot even be opened on dce80 today. The defect is real and proven, but latent: it becomes live the moment anything starts calling dal_gpio_get_pin_info (e.g. future BSP code), at which point a wrong-mask gpio_pin_info would describe register writes to the wrong GPIO bit.

After fix.diff

result = (en <= GPIO_GPIO_PAD_MAX) before computing the mask: pads 5..30 accepted with correct masks, enβ‰₯32 rejected β€” fixrun.log T4:

id_to_offset(GPIO_PAD,en=5)  = true mask=0x20
id_to_offset(GPIO_PAD,en=30) = true mask=0x40000000
id_to_offset(GPIO_PAD,en=32) = false
id_to_offset(GPIO_PAD,en=64) = false
VERDICT.md
↓ download raw

DF-4944 VERDICT

Status: reproduced (logic + UB acceptance executed on the DF guest); latent (no live callers).

  • Verbatim copy of id_to_offset executed on the guest: the GPIO_PAD branch at hw_translate_dce80.c:378-382 computes info->mask = (1 << en) and then compares mask against GPIO_GPIO_PAD_MAX (30) β€” an index bound (gpio_types.h:156). Result (run.log T4):
  • valid pads en=5, en=30 β†’ rejected (masks 0x20/0x40000000 > 30);
  • invalid en=32, en=64 β†’ accepted with mask == 0x0 on gcc 8.3 x86-64 (the 1 << en UB manifests as 0; 0 <= 30 passes).
  • Severity stays Info because the only route to this code is dal_gpio_get_pin_info (gpio_base.c:140-147), which has zero callers in sys/ (verified by tree-wide grep: definition + declaration only), and GPIO_PAD pins cannot be opened anyway (no GPIO_ID_GPIO_PAD case in dal_gpio_service_open, gpio_service.c:243-274). The defect is proven, not currently reachable β€” it is a loaded trap for the first future caller.
  • Negative checks preserved by the fix: GPIOPAD reverse-map bounds (offset_to_id) are sound β€” 31/UNKNOWN(32)/multi-bit all rejected (T3); HPD forward-map rejects en>=6 (T5).
  • fix.diff validates the index before shifting; harness_fixed confirms both halves corrected with 0 regressions (fixrun.log T4).

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 reproduced→ patch + rebuild →patched clean

fix.diff replaces the mask comparison with an index comparison before shifting (result = (en <= GPIO_GPIO_PAD_MAX); if (result) info->mask = (1 << en)): harness_fixed shows pads 5..30 accepted with correct masks and en>=32 rejected, 0 regressions (fixrun.log T4).

fixrun.log T4 section (four lines listed in README); fix.diff hunk 2 of the dce80 file.
↓ fix.diffper-fix-DF-4944

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log T4: en=5/en=30 (VALID) rejected; en=32/en=64 (INVALID) accepted via UB with mask=0x0; fixrun.log T4 after fix.diff: en=5 mask=0x20 and en=30 mask=0x40000000 accepted, en=32/64 rejected.

PoC changes

n/a β€” fresh harness (verbatim function copy, real constants).

Verified recommended fix

Validate the pin index (en <= GPIO_GPIO_PAD_MAX) before computing info->mask = (1 << en).

Verdict

id_to_offset's GPIO_ID_GPIO_PAD branch (hw_translate_dce80.c:378-382) validates the derived mask (1<=32 hits the C11 6.5.7 UB shift and is accepted with a degenerate mask (proved mask==0 accepted on the guest's gcc 8.3/x86-64 β€” run.log T4). Both halves executed on the DF guest with the verbatim function copy. Impact is Info-class only because the sole caller, dal_gpio_get_pin_info (gpio_base.c:140-147), has no callers in the tree and GPIO_PAD pins cannot be opened (no case in dal_gpio_service_open, gpio_service.c:243-274) β€” a proven, latent logic/UB defect.