ata_intel_chipinit treats any non-ENXIO return of ata_ahci_chipinit as AHCI success, leaving ctlr->setmode NULL β latent boot-time NULL function-pointer call (fragile error contract)
Summary
The '!= ENXIO' guard accepts error codes as success and returns 0, skipping the compat fallback that is the only remaining setmode assignment - the callee propagates reset errors verbatim and assigns setmode only after that point, so a future third error code yields an attached controller with setmode==NULL and the unconditional call in ata_pcichannel_setmode becomes a NULL fn-ptr panic at disk attach. Exhaustively verified dead on this tree (all four callee exits return {0,ENXIO}; the guest's PIIX3 boot path runs in-bounds every boot - live negative evidence in pack). == 0 fix to pin the contract.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4867 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| collect_live.sh | β | 668 B | view raw | |
| dmesg.txt | β | 237 B | view raw | |
| env.txt | β | 456 B | view raw | |
| fix.diff | β | 471 B | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-4867 β latent NULL ctlr->setmode call: ata_intel_chipinit treats any non-ENXIO return of ata_ahci_chipinit as AHCI success
- File:
sys/dev/disk/nata/chipsets/ata-intel.c - Severity: Info (hardening / fragile error-contract), confidence: certain (shape) / latent (reachability)
- Verdict: not_reproduced β traced dead on this tree (see VERDICT.md). Not a bug today; one refactor away from a boot-time NULL function-pointer call.
What the finding is
ata_intel_chipinit() (ata-intel.c:177-180) probes for AHCI mode:
if ((ctlr->chip->cfg1 == INTEL_AHCI) &&
(pci_read_config(dev, 0x90, 1) & 0xc0) &&
(ata_ahci_chipinit(dev) != ENXIO))
return 0;
Any return value that is not exactly ENXIO β including any other error code β is
treated as AHCI success and chipinit returns 0 immediately, skipping the compat-mode
fallback at ata-intel.c:182-189 that is the only remaining place ctlr->setmode
gets assigned on this path.
ata_ahci_chipinit() (ata-ahci.c:71-122) has three failure exits:
- BAR(5) alloc failure β ENXIO (ata-ahci.c:82)
- interrupt setup failure β ENXIO (ata-ahci.c:88)
- ata_ahci_ctlr_reset() failure β return (error) β propagated verbatim (ata-ahci.c:95-98)
All of ctlr->reset/dmainit/allocate/setmode and ctlr->channels are assigned only
after that point (ata-ahci.c:101-108). So if ata_ahci_ctlr_reset() ever returns
anything other than ENXIO (it currently returns only ENXIO/0, but its error is
propagated as a variable), ata_intel_chipinit returns 0 with:
ctlr->setmode == NULL(compat block skipped, AHCI assignments never reached)- controller attach continues; channel attach then runs
ata_pcichannel_setmode()(ata-pci.c:857-866) βctlr->setmode(dev, ATA_PIO_MAX)at ata-pci.c:863 β NULL function-pointer call β kernel panic during boot/attach (sink chain: ata-all.c:525 / ata-disk.c:436 β ATA_SETMODE method).
Fix
Take the AHCI path only on explicit success β see fix.diff:
- (ata_ahci_chipinit(dev) != ENXIO))
+ (ata_ahci_chipinit(dev) == 0))
Pack contents
VERDICT.mdβ full narrative + line-level trace + what was live-verifieddmesg.txtβ guest boot evidence: the audited ata-intel path runs on every bootenv.txtβ guest uname, PCI config dump of atapci0 (PIIX3), device idcollect_live.shβ exact commands to re-collect the evidencefix.diffβ git-apply-able one-line hardeningmanifest.json/verdict.json
How to re-run the live evidence
dfbsd-qemu/vm.sh up # if down ./collect_live.sh # or: dfbsd-qemu/vm.sh run_root 'dmesg | grep -E "atapci0|ata[01]:"'
Expected: atapci0: <Intel PIIX3 WDMA2 controller> + ata0/ata1 channels β proving
ata_intel_ident β ata_intel_chipinit β non-SATA branch β ata_intel_new_setmode
(timing tables, ata_mode2idx) executed in-bounds at boot with no anomaly.
DF-4867 VERDICT β not_reproduced (latent; path traced dead on this tree)
Bottom line
The fragile error-contract at ata-intel.c:177-180 is real and certain in shape, but
it is not reachable as a bug on this tree today: ata_ahci_chipinit() can only
return 0 or ENXIO, so the != ENXIO test never mistakes a failure for success.
No runtime manifestation is possible, hence not_reproduced / impact none. The
finding is filed as hardening: the guard is one refactor away from a boot-time NULL
function-pointer call, because ata_ahci_chipinit() propagates
ata_ahci_ctlr_reset()'s error verbatim (return (error); at ata-ahci.c:97)
and that callee's return set is not enforced by any contract.
Line-level trace
-
sys/dev/disk/nata/chipsets/ata-intel.c:177-180c if ((ctlr->chip->cfg1 == INTEL_AHCI) && (pci_read_config(dev, 0x90, 1) & 0xc0) && (ata_ahci_chipinit(dev) != ENXIO)) return 0;Entering the SATA branch requireschip->max_dma >= ATA_SA150(ata-intel.c:159/166), i.e. cfg1==INTEL_AHCI entries only (ICH6+ AHCI/RAID SKUs, ata-intel.c:71-112). Requires config 0x90 bits 0xc0 set (BIOS AHCI/RAID mode). -
ata_ahci_chipinit()return paths (sys/dev/disk/nata/chipsets/ata-ahci.c): - :80-82 BAR(5) MEMORY alloc fails βENXIO- :85-89ata_setup_interruptfails βENXIO- :95-98ata_ahci_ctlr_reset()!= 0 βreturn (error)β any error code propagates - :121 success β0(only here arectlr->channels/:105-108 setmodeetc. assigned)ata_ahci_ctlr_reset()(:124-159) currently returns onlyENXIO(:141) or0(:159). -
If a non-ENXIO error ever propagated:
ata_intel_chipinitreturns 0 at :180 withctlr->setmode == NULLβ the compat block at :182-189 (ctlr->setmode = ata_intel_sata_setmodeorata_sata_setmode) is skipped, and the AHCI block never assigned it. -
Sink:
ata_pcichannel_setmode()ata-pci.c:857-866 callsctlr->setmode(dev, ATA_PIO_MAX)(:863) unconditionally via theata_setmodebus method, invoked from ata-all.c:525 and ata-disk.c:436 during channel/device attach β NULL function pointer call β panic while attaching the disk subsystem.
Why not reproducible on this guest / tree
- Hardware path: requires an ICH6+ Intel SATA controller with cfg1==INTEL_AHCI, BIOS mode bits 0x90&0xc0, and an AHCI controller-reset failure returning non-ENXIO.
- Source path: with the current tree,
ata_ahci_chipinitβ {0, ENXIO} at every exit (verified by exhaustively reading all four return sites), so the!= ENXIOguard never misfires. There is no way to makeata_ahci_ctlr_resetreturn a third value without editing sys/ (read-only for this audit). - Guest (QEMU PIIX3, see env.txt) has no AHCI-capable Intel SATA part at all: the
boot path is the non-SATA branch (ata-intel.c:159-163), which always assigns
ctlr->setmode = ata_intel_new_setmode.
What WAS live-verified (negative evidence for this file's boot path)
The guest boots on atapci0: <Intel PIIX3 WDMA2 controller> (chip 0x70108086 =
ATA_I82371SB, ata-intel.c:51 β ata_intel_chipinit β non-SATA branch, channels=2,
ata_intel_allocate, ata_intel_new_setmode):
- dmesg.txt:
atapci0,ata0,ata1,acd0 ... WDMA2β the setmode callback ran at attach and indexedtimings[ata_mode2idx(WDMA2)]=timings[7](in-bounds, table has 15 entries) with no anomaly. - env.txt: PCI config dword at 0x54 = 0x00000400 β exactly the vendor-specific bit
reg54 |= 0x0400written byata_intel_new_setmode(ata-intel.c:310). Direct register-level proof the audited function executed on this boot. - Guest healthy throughout; no panic; guest not dirtied.
Fix validation
Not applicable (nothing to make fail on the baseline). The one-line fix.diff
(!= ENXIO β == 0) is trivially semantics-preserving for every return value the
callee can produce today and closes the contract hole for any future error code.
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 validation N/A: the latent path cannot be made to fail on the baseline tree without modifying sys/ (read-only). The == 0 guard is semantics-preserving for every return value producible today and eliminates the contract hole.
findings/poc/DF-4867/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (full path:line trace and reachability analysis); dmesg.txt (PIIX3 boot through ata_intel_chipinit non-SATA branch + ata_intel_new_setmode at attach); env.txt (uname, pciconf 0x40:0x5f dump showing 0x54=0400 from ata-intel.c:310); fix.diff (one-line guard fix)
PoC changes
No PoC needed (Info hardening, latent). Replaced trigger with a live negative-evidence collector (collect_live.sh) proving the audited chip-init/setmode path executes in-bounds on this guest at every boot.
Verified recommended fix
Only take the AHCI path on explicit success: ata_ahci_chipinit(dev) == 0 (not != ENXIO) in ata_intel_chipinit
Verdict
Latent-by-analysis: ata_intel_chipinit (ata-intel.c:177-180) accepts ANY non-ENXIO return of ata_ahci_chipinit as AHCI success and returns 0 without assigning ctlr->setmode (compat fallback at :182-189 skipped; AHCI assigns it only on the success path at ata-ahci.c:108). A third error code β e.g. any future value propagated verbatim by return(error) at ata-ahci.c:97 from ata_ahci_ctlr_reset β would leave ctlr->setmode NULL and panic the kernel at boot via the unconditional ctlr->setmode(dev, ATA_PIO_MAX) call in ata_pcichannel_setmode (ata-pci.c:863, invoked from ata-all.c:525 / ata-disk.c:436). Not reachable on this tree: exhaustively verified all four exits of ata_ahci_chipinit return only {0, ENXIO}, and the guest (PIIX3, non-SATA branch) has no AHCI-capable Intel SATA hardware. Live-verified instead that the audited boot path runs cleanly every boot: dmesg shows atapci0
No comments yet.