From 9c571e294298d9d080751e2e1f5840a8234c89b8 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Wed, 22 Jul 2026 14:20:57 +0900 Subject: [PATCH] pcid: guard config access against out-of-range offsets (fixes boot panic) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An extended-capability walk (pci_types capabilities()) that follows a chain running to/past the end of config space read at offset 4096, which tripped the dword-offset assert in bus_addr_offset_in_dwords ("pcie offset larger than 4095") and aborted pcid with an Invalid opcode fault / UNHANDLED EXCEPTION on boot — after switchroot to /usr, so it blocked reaching login. PCIe config space is exactly 4096 bytes (offsets 0..=4095). Fix at two levels: - ConfigRegionAccess::read/write (cfg_access/mod.rs): guard the single access choke point — an out-of-range read returns the PCI "no response" pattern (0xFFFFFFFF, which also terminates a capability walk cleanly) and an out-of-range write is a no-op. This catches any caller (capability walk, scheme reads, driver access). - scheme.rs config read/write loop: clamp the loop bound with saturating_add(...).min(4096) so a consumer requesting offset+len past the boundary reads only the valid part instead of looping to offset 4096. Verified: boot no longer panics in pcid (progresses past the previous crash point through switchroot and the /usr driver set). --- drivers/pcid/src/cfg_access/mod.rs | 17 +++++++++++++++++ drivers/pcid/src/scheme.rs | 13 ++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/pcid/src/cfg_access/mod.rs b/drivers/pcid/src/cfg_access/mod.rs index 1abd29a02d..b23bdd4e73 100644 --- a/drivers/pcid/src/cfg_access/mod.rs +++ b/drivers/pcid/src/cfg_access/mod.rs @@ -404,8 +404,22 @@ impl Pcie { } } +/// PCIe configuration space is exactly 4096 bytes: valid offsets are +/// `0..=4095`. `bus_addr_offset_in_dwords` asserts this. A caller that runs off +/// the end — most easily an extended-capability walk (`pci_types` +/// `capabilities()`) following a chain whose next-pointer lands at or past the +/// boundary — would otherwise abort pcid with an "Invalid opcode fault" / +/// UNHANDLED EXCEPTION on boot. Guard the single access choke point: an +/// out-of-range read returns the PCI "no response" pattern (all ones, which +/// also terminates a capability walk cleanly) and an out-of-range write is a +/// no-op. +const PCIE_CONFIG_SPACE_LEN: u16 = 4096; + impl ConfigRegionAccess for Pcie { unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 { + if offset >= PCIE_CONFIG_SPACE_LEN { + return 0xFFFF_FFFF; + } let _guard = self.lock.lock().unwrap(); match self.mmio_addr(address, offset) { @@ -415,6 +429,9 @@ impl ConfigRegionAccess for Pcie { } unsafe fn write(&self, address: PciAddress, offset: u16, value: u32) { + if offset >= PCIE_CONFIG_SPACE_LEN { + return; + } let _guard = self.lock.lock().unwrap(); match self.mmio_addr(address, offset) { diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs index c748b7b46d..135db0006a 100644 --- a/drivers/pcid/src/scheme.rs +++ b/drivers/pcid/src/scheme.rs @@ -311,7 +311,18 @@ impl SchemeSync for PciScheme { // lower level only works with 4 byte reads and writes let unaligned = offset % 4; let start = offset - unaligned; - let end = offset + payload_len; + // PCIe extended config space is exactly 4096 bytes (valid + // offsets 0..=4095). A request whose offset+len runs past the + // end must be clamped here — otherwise the loop below issues a + // read/write at offset 4096, which trips the dword-offset + // assert in bus_addr_offset_in_dwords and aborts pcid (observed + // as an "Invalid opcode fault" / UNHANDLED EXCEPTION on boot + // when a consumer reads config near the boundary). Bytes past + // the boundary are left untouched in the caller's payload. + const PCIE_CONFIG_SPACE_LEN: u16 = 4096; + // saturating_add so a large payload_len cannot wrap u16 and + // defeat the clamp. + let end = offset.saturating_add(payload_len).min(PCIE_CONFIG_SPACE_LEN); let mut i = 0; while start + i < end { let mut bytes = unsafe { self.pcie.read(addr, start + i) }.to_le_bytes();