CIS-controlled PCMCIA I/O window size drives out-of-allocation port I/O in ata_pccard_attach()
Summary
The pccard bus returns the CIS-declared window verbatim - lengths 9..14 put ATA_CONTROL at fixed offset 14 outside the allocation, lengths 1..7 leave offsets 0..7 outside; the ALTSTAT alias makes both the control write and the status-poll read paths poke adjacent host device ports. Physical-card trigger, no kernel memory access. Validate-window-before-wiring fix (git-apply verified).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5175 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.6 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| cis_window_trace.md | β | 2.0 KB | β raw | |
| fix.diff | β | 1.2 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-5175 β CIS-controlled PCMCIA I/O window size drives out-of-allocation port I/O in ata_pccard_attach()
What
sys/dev/disk/nata/ata-card.c wires ATA register access offsets onto the I/O
resource returned by the pccard bus without validating that the resource
actually covers those offsets. On 16-bit PCMCIA, the size of that resource is
the I/O window length declared in the card's CIS (CFE tuple), i.e. fully
card-controlled. Two windows of sizes make the wiring go out of bounds:
- size 9..14 β
rman_get_size(io) > ATA_IOSIZE(ata-card.c:103) is true, soATA_CONTROLis mapped at fixed offset 14 into the window (ata-card.c:104-106). A 1-byte access atwindow_start+14lies outside the allocation for every size β€ 14. - size 1..7 β the
> ATA_IOSIZEtest is false, the driver takes the separate-ctlio branch, and the primary registers are wired at offsets 0..7 (ata-card.c:93-96) against a window shorter than 8 ports.
Every subsequent ATA control-path access then performs out-of-allocation I/O:
- writes:
ATA_IDX_OUTB(ch, ATA_CONTROL, β¦)fromata_generic_reset()/ command setup (sys/dev/disk/nata/ata-lowlevel.c) β 1-byte writes of the ATA control byte (values from the small set 0x00/0x02/0x04/0x08/0x0a/0x0eβ¦); - reads:
ATA_ALTSTATis aliased onto the same resource+offset byata_default_registers()(sys/dev/disk/nata/ata-all.c:734-735) and polled for status.
For the size<8 case the OOB offsets 1..7 receive command/LBA/feature bytes (semi-attacker-influenced values once the "disk" answers IDENTIFY).
Precondition / threat model
- Attacker must physically insert a malicious 16-bit PCMCIA card whose CIS
declares
PCCARD_FUNCTION_DISK(probe matches unconditionally, ata-card.c:63-67) and a first CFE I/O window of length 9..14 (or 1..7). - CardBus (32-bit) cards do not go through this file β they attach via
the PCI path (
ata-pci.c); this file registers on thepccardbus only (ata-card.c:163). - Impact ceiling: 1-byte port pokes at fixed offsets just past (or within a truncated) PCMCIA window. The bridge window is programmed with the exact CIS length (byte granularity), so the stray accesses stay on the host bus and can land in a neighboring device's port range (serial / FDC / PIT class registers live in the same allocator region). No kernel memory is touched; worst case is perturbation of adjacent hardware (device malfunction, potential system instability).
Severity: Low (AV:P β physical insertion required). This is a robustness/defense-in-depth bound-validation bug, not a memory-corruption bug.
Why untested here
The verification guest (QEMU/KVM, virtio hardware) has no PCMCIA/cbb
controller and no slot, so the trigger cannot be instantiated. The proof
below is a complete code-level trace from card-controlled CIS bytes to the
bus_space_* call. Reproducing on real hardware requires a programmable
PCMCIA card or a bridge-level emulator declaring the crafted CFE.
Reference trigger (for hardware testers)
CIS contents needed (attribute memory):
- CISTPL_DEVICE / CISTPL_VERS_1: minimal valid card header;
- CISTPL_FUNCID: function = 4 (disk);
- CISTPL_CONFIG + CISTPL_CFTABLE_ENTRY: IO space descriptor with
num_iospace = 1, window length 10 (0x09 stored, +1 by the parser), addr size 2 bytes, start = 0 (any); - one IRQ line.
Expected observable: ata_pccard_attach succeeds; during
ata_attach β ATA_RESET, an outb of the control byte lands on
window_base + 14, i.e. 4 ports past the 10-port window owned by the card.
A logic analyzer on the host bus, or an I/O-port trap on a hypervisor that
models a pccard bridge, shows the access outside the programmed window.
Reproduce (code-level)
Read VERDICT.md β it traces every hop with path:line:
CIS CFE bytes (card-controlled)
β pccard_cis.c:1076-1092 length = tuple bytes + 1 (no 8/16 constraint)
β pccard.c:429-449 window pre-allocated with exactly that length
β pccard.c:1121-1149 child bus_alloc_resource returns the window
verbatim; requested count ATA_IOSIZE ignored
β ata-card.c:103-106 size>8 β ATA_CONTROL at offset 14 (OOB for β€14)
β ata-card.c:93-96 offsets 0..7 wired unconditionally (OOB for <8)
β ata-all.c:734-735 ATA_ALTSTAT aliased to same res+offset
β ata-lowlevel.c ATA_IDX_OUTB/INB β bus_space_write/read_1 at
window_start+14 (or +1..+7) β outside allocation
No build is required or possible for this pack (no target hardware in the
audit environment). fix.diff is the verified-by-inspection fix.
DF-5175 β VERDICT
Status: untested (hardware-gated; Low severity β Phase V skipped per
audit contract). Not a false positive: the defect is proven by complete
code-level trace below; only runtime demonstration is impossible in the
audit environment (QEMU guest has no PCMCIA controller / slot).
Reproduced?
No. Neither a physical PCMCIA slot nor a pccard-bridge emulator exists in the
audit guest. Impact therefore recorded as none (not demonstrated),
confidence likely (every link in the chain is source-provable; the only
empirical unknown is real-hardware window programming, which is dictated by
the ExCA/cbb window registers taking the exact CIS length).
The chain, hop by hop
-
CIS parse β window length is raw card bytes.
sys/bus/pccard/pccard_cis.c:1074-1092: forPCCARD_TPCE_IO_RANGE_LENGTHSIZE_{ONE,TWO,FOUR},cfe->iospace[i].length = pccard_tuple_read_{1,2,4}(tuple, idx)thenlength++. Any length 1..2^32 is accepted; there is no 8/16-only constraint anywhere in the parser. -
Bus pre-allocates the window with exactly the CIS length.
sys/bus/pccard/pccard.c:429-449(pccard_function_init): for each CFE IO space,len = cfe->iospace[i].length;bus_alloc_resource(bus, SYS_RES_IOPORT, &rid=i, start, end, len, rman_make_alignment_flags(len)). Alignment is pow2-rounded placement; the resource size stayslen.resource_list_addrecords the same length. Noterid = iβ the first CIS window becomes the child'sATA_IOADDR_RID (0)resource. -
Child allocation returns the CIS-sized window, requested count ignored.
sys/dev/disk/nata/ata-card.c:87-90asks forATA_IOSIZE(8) ports, butpccard_alloc_resource(sys/bus/pccard/pccard.c:1121-1149) finds the pre-existingrlefor rid 0 (set in step 2) and returnsrle->resverbatim β the requestedcountis never compared against the window. Hencerman_get_size(io)atata-card.c:103is the card-chosen CIS length, not 8. -
Unvalidated offset wiring. *
ata-card.c:93-96: offsets 0..7 wired for ATA_DATA..ATA_COMMAND (OOB whenever CIS length < 8). *ata-card.c:103-106:rman_get_size(io) > ATA_IOSIZE(β₯9) βATA_CONTROLat offset 14 (ATA_PCCARD_CTLOFFSET,sys/dev/disk/nata/ata-all.h:95). A 1-byte access at +14 is inside the allocation only for length β₯ 15. CIS lengths 9..14 make every control access out-of-allocation. *ata-card.c:119βata_default_registers()(sys/dev/disk/nata/ata-all.c:723-735) aliasesATA_ALTSTATonto the same{res, offset}β so the status-poll read path is OOB too. -
Sinks.
ATA_IDX_OUTB/INBexpand tobus_space_write_1/read_1(rman_get_bustag(res), rman_get_bushandle(res), offset, β¦)(sys/dev/disk/nata/ata-all.h:679-712) β x86outb/inbatwindow_base+offset, no bounds checks. Control writes happen inata_generic_reset()/ata_generic_command()(ata-lowlevel.c) duringata_attachβATA_RESET(sys/dev/disk/nata/ata-all.c:128-131), i.e. on the very first attach of the malicious card; status reads poll ATA_ALTSTAT. -
Reachability gate.
ata_pccard_probereturns 0 for anyPCCARD_FUNCTION_DISKcard (ata-card.c:63-67) β no product-table match needed. The driver registers on the pccard bus only (ata-card.c:163); CardBus cards use the PCI path (ata-pci.c) and are unaffected by this file's wiring.
Why this is NOT DF-2173 (known, not re-reported)
DF-2173 = resource leak (io/ctlio orphaned when ata_probe/ata_attach
fail, ata-card.c:125-128, because newbus never calls detach after failed
attach). DF-5175 = out-of-allocation port I/O on the success path caused
by trusting the CIS window size when wiring register offsets. Different sink
(r_io offsets vs. resource lifetime), different trigger (CIS geometry vs.
attach failure), different code lines (93-106 vs 125-128).
Impact ceiling (honest)
1-byte port writes with near-fixed values (control byte bits IDS/RESET/4BIT/
HOB) at window+14, or semi-controlled command/LBA bytes at window+1..7
for short windows, hitting whatever host device owns the adjacent ports.
No kernel memory is read or written; no privilege boundary is crossed by
software. Physical access required. β Low, hardening/robustness.
Fix validation
Not runnable (same hardware gate). fix.diff fails the attach closed for
windows < 8 and only takes the shared-control-at-14 path for windows β₯ 15
(ATA_PCCARD_CTLOFFSET + ATA_CTLIOSIZE), both expressed with the existing
named constants. Authored against the read-only sys/ tree; never applied
there.
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.
DF-5175 β code-level proof chain (the "trigger" is card attribute memory, not a program)
GATE sys/dev/disk/nata/ata-card.c:63-67 fcn == PCCARD_FUNCTION_DISK => probe matches ANY disk card ALLOC sys/dev/disk/nata/ata-card.c:87-90 bus_alloc_resource(..., ATA_IOADDR_RID=0, 0, ~0, ATA_IOSIZE=8, RF_ACTIVE) WINDOW sys/bus/pccard/pccard.c:429-449 window pre-allocated by pccard with len = CIS iospace[0].length RETURN sys/bus/pccard/pccard.c:1121-1149 pccard_alloc_resource returns rle->res verbatim; count(8) NEVER checked vs window CIS-LEN sys/bus/pccard/pccard_cis.c:1074-1092 length = raw tuple byte(s) + 1 ; any value accepted WIRE-A sys/dev/disk/nata/ata-card.c:93-96 r_io[0..7].offset = i => OOB if length < 8 WIRE-B sys/dev/disk/nata/ata-card.c:103-106 size > 8 => r_io[ATA_CONTROL].offset = 14 => OOB if length <= 14 ALIAS sys/dev/disk/nata/ata-all.c:734-735 ATA_ALTSTAT <= r_io[ATA_CONTROL] (same res+offset, read path OOB too) RESET sys/dev/disk/nata/ata-all.c:128-131 ata_attach -> ATA_RESET on first attach SINK sys/dev/disk/nata/ata-all.h:679-712 ATA_IDX_OUTB/INB => bus_space_write_1/read_1(bustag, bushandle, offset) HOST x86: outb/inb at window_base+offset, outside the rman allocation and outside the bridge window (ExCA window programmed with the exact CIS length, byte granularity)
Reference CIS geometry for hardware testers (16-bit PCMCIA attribute memory): CISTPL_DEVICE, CISTPL_VERS_1 minimal card header CISTPL_FUNCID function = 4 (disk) CISTPL_CONFIG + CISTPL_CFTABLE_ENTRY TPCE_IO with HASRANGE, num_iospace=1, LENGTHSIZE_ONE, length byte = 0x09 (=> 10 ports) => 10 > ATA_IOSIZE(8) but 10 <= 14 => control at +14 is 4 ports OOB variant: length byte = 0x00 (=> 1 port) => primary regs at offsets 0..7 on a 1-port window
Fix verification
not_testablefix.diff authored against read-only sys/ and git-apply-verified on a pristine copy; runtime fix validation impossible for the same hardware reason as the trigger (no PCMCIA controller in any available environment); changes fail the attach closed for undersized windows and make the shared-control branch require a window that actually covers offset 14
findings/poc/DF-5175/fix.diff; verified 'APPLIES_CLEAN' + 'MATCHES_INTENDED' against a pristine copy of sys/dev/disk/nata/ata-card.c
Confirmed kernel references
- sys/dev/disk/nata/ata-card.c:93-96
- sys/dev/disk/nata/ata-card.c:103-106
- sys/dev/disk/nata/ata-card.c:87-90
- sys/dev/disk/nata/ata-card.c:63-67
- sys/bus/pccard/pccard.c:429-449
- sys/bus/pccard/pccard.c:1121-1149
- sys/bus/pccard/pccard_cis.c:1074-1092
- sys/dev/disk/nata/ata-all.c:723-735
- sys/dev/disk/nata/ata-all.h:679-712
Detail
Evidence (decisive lines)
findings/poc/DF-5175/VERDICT.md (hop-by-hop path:line trace); cis_window_trace.md (chain + reference CIS geometry); fix.diff (applies clean, verified with git apply --check on a pristine copy)
PoC changes
n/a β no seeded PoC existed; the pack documents a code-level proof chain plus reference CIS geometry for hardware testing instead of an executable
Verified recommended fix
Validate rman_get_size(io) in ata_pccard_attach: fail closed for windows < ATA_IOSIZE, and only use the shared control-at-offset-14 layout when size >= ATA_PCCARD_CTLOFFSET + ATA_CTLIOSIZE (see fix.diff)
Verdict
Untested at runtime because the trigger requires a malicious 16-bit PCMCIA card in a physical slot and the QEMU verification guest has no pccard/cbb controller; the defect itself is fully proven by source trace. The pccard bus returns the CIS-declared I/O window verbatim to ata_pccard_attach (pccard.c:1121-1149, window sized at pccard.c:429-449 from raw CIS bytes at pccard_cis.c:1074-1092), and ata-card.c wires register offsets without validating coverage: window lengths 9..14 put ATA_CONTROL at fixed offset 14 (ata-card.c:103-106) outside the allocation, lengths 1..7 leave primary register offsets 0..7 (ata-card.c:93-96) outside it; reads are aliased through ATA_ALTSTAT (ata-all.c:734-735). Result is 1-byte out-of-allocation port I/O (near-fixed control-byte writes, status reads) at fixed offsets near the window β adjacent-host-device perturbation at worst, no kernel memory access, physical access required. Low severity; Phase V PoC/fix-build skipped per audit contract for Low hardening findings; fix.diff authored and git-apply-verified against the pristine tree.
No comments yet.