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)
PoC verification
Evidence pack
findings/poc/DF-4944 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 << enwithen >= 32is undefined (C11 6.5.7); on the guest's gcc 8.3/x86-64 the UB manifests asmask == 0, and0 <= 30is 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
DF-4944 VERDICT
Status: reproduced (logic + UB acceptance executed on the DF guest); latent (no live callers).
- Verbatim copy of
id_to_offsetexecuted on the guest: the GPIO_PAD branch at hw_translate_dce80.c:378-382 computesinfo->mask = (1 << en)and then compares mask againstGPIO_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 == 0x0on gcc 8.3 x86-64 (the1 << enUB manifests as 0;0 <= 30passes). - 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 (noGPIO_ID_GPIO_PADcase indal_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_testablefix.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.
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<
No comments yet.