pcid: guard config access against out-of-range offsets (fixes boot panic)

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).
This commit is contained in:
Red Bear OS
2026-07-22 14:20:57 +09:00
parent a12fb9fc7c
commit 9c571e2942
2 changed files with 29 additions and 1 deletions
+17
View File
@@ -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) {
+12 -1
View File
@@ -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();