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:
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user