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

Dead debug printers carry latent unvalidated firmware-count walks (mpr_print_evt_sas NumEntries) and mpr_describe_table relies on a sentinel-plus-one convention

Summary

mpr_print_evt_sas walks data->PHY[i] for firmware-supplied NumEntries with no EventDataLength bound (zero callers tree-wide - the same class pass-1 filed against live consumers DF-1474/DF-3919/DF-4560); describe_table returns table[i+1] one past the {NULL,0} sentinel, in-bounds only by the extra-Unknown convention in all 13 tables. Bound-or-delete fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4790 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 2.0 KB ↓ raw
env.txt β€” 920 B view raw
fix.diff β€” 832 B view raw
manifest.json β€” 499 B view raw
verdict.json β€” 1.9 KB view raw

DF-4790 β€” latent unvalidated event-count walks in dead debug printers; sentinel-plus-one convention in mpr_describe_table

What

sys/dev/raid/mpr/mpr_table.c contains two latent defects in code that is currently dead (no callers anywhere in sys/ β€” verified with tree-wide grep; only mpr_print_scsiio_cmd/mpr_print_sgl and mpr_describe_devinfo/mpr_describe_table are live):

  1. mpr_print_evt_sas topology walk (mpr_table.c:412-425). c for (i = 0; i < data->NumEntries; i++) { phy = &data->PHY[i]; NumEntries is a firmware-supplied byte inside the event reply and is never checked against EventDataLength. The MPI2 event structure carries PHY[] for the entries actually delivered; a short frame with a large NumEntries walks past the event buffer (OOB heap read printed to console). This is the same defect class pass 1 filed against the live consumers (DF-1474 on mpr_sas_lsi.c, DF-3919 on mpr_mapping.c, mps twin DF-4560) β€” but in mpr_table.c the function has no callers, so it is latent. If anyone ever wires this printer up (its mps counterpart was historically called from the event handler), the unbounded walk comes back. Recommended resolution: bound the loop by EventDataLength * 4 (fix.diff) or delete the dead printer.

  2. mpr_describe_table sentinel-plus-one (mpr_table.c:63-73). c for (i = 0; table[i].string != NULL; i++) { ... } return(table[i+1].string); The fallback intentionally indexes one entry past the {NULL, 0} sentinel. All 13 tables in this file happen to carry an extra {"Unknown …", 0} entry after the sentinel (mpr_table.c:107-108, 117-118, 131-132, 139-140, 152-153, 162-163, 169-170, 178-179, 194-195, 204-205, 226-227, 242-243, 255-256), so today it is in-bounds β€” but any future table that ends at the sentinel turns the "unknown code" lookup into an OOB pointer read + %s deref. Fragile-by-convention, worth a defensive bound or a literal fallback string.

Also noted, benign: mpr_table.c:457 β€” the MPI2_EVENT_SAS_DEVICE_STATUS_CHANGE case falls through to default: (missing break;); default: break; makes it harmless.

Impact

None today (dead code / in-bounds-by-convention). Filed as Info hardening so the patterns don't get resurrected as-is.

Verdict

untested β€” latent code-path finding; nothing to execute.

VERDICT.md
↓ download raw

DF-4790 VERDICT

  • Status: untested (latent / dead code)
  • Confidence: certain (dead-code status and loop bodies verified by direct read + tree-wide grep)
  • Severity filed: Info

Evidence

Liveness check (tree-wide, rg over sys/): the only callers of any mpr_print_* symbol outside mpr_table.c are

  • mpr_sas.c:2814 β†’ mpr_print_scsiio_cmd (live; covered by DF-4789)
  • mpr_sas_lsi.c:967,1381 β†’ mpr_describe_devinfo (live; bounded ksnprintf into an 80-byte stack buffer β€” checked safe)

mpr_print_evt_sas, mpr_print_evt_generic, mpr_print_iocfacts, mpr_print_portfacts, mpr_print_sasdev0, mpr_print_expander1, mpr_print_sasphy0 have zero callers. Their latent defects:

  1. mpr_table.c:412-425 β€” NumEntries (firmware byte) drives data->PHY[i] with no EventDataLength bound. Compare the live twins already filed in pass 1: DF-1474 (mpr_sas_lsi.c topo/IR/PCIe walks), DF-3919 (mpr_mapping.c element counts), DF-4560 (mps mpssas_fw_work fixed-offset reads). Same class, currently unreachable.
  2. mpr_table.c:72 β€” table[i+1].string relies on every table shipping an extra entry after the {NULL,0} sentinel; all 13 do today (line refs in README), so this is convention-fragility, not a live OOB.

Why Info and not higher

Both arms require the dead code to be wired up first. No reachable path exists on any configuration, hardware or otherwise; nothing to execute on the guest.

Bound the NumEntries walk by the event-frame length (fix.diff) β€” or better, delete the dead printers; replace table[i+1].string with a literal "unknown" fallback or bounds-check i+1.

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

dead code β€” nothing to validate on the guest; the diff is a defensive bound only

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

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (liveness + line cites), VERDICT.md (grep evidence of dead callers), fix.diff (NumEntries bound)

PoC changes

no PoC produced β€” dead code, no trigger path exists

Verified recommended fix

Delete the dead mpr_print_* debug printers or bound NumEntries by EventDataLength; use a literal fallback in mpr_describe_table

Verdict

Latent, currently-dead defects in mpr_table.c: (1) mpr_print_evt_sas walks data->PHY[i] for firmware-supplied NumEntries with no EventDataLength bound (mpr_table.c:412-425) β€” the same class as live pass-1 findings DF-1474/DF-3919/DF-4560, but this function has zero callers tree-wide; (2) mpr_describe_table returns table[i+1].string, one entry past the {NULL,0} sentinel (mpr_table.c:72) β€” in-bounds today only because all 13 tables ship an extra 'Unknown' entry. No reachable path, nothing to execute; filed as Info so the patterns are not resurrected as-is.