virtio-devices: Respect PCI CFG cap.length for BAR access

The VIRTIO_PCI_CAP_PCI_CFG indirect access mechanism was ignoring
the cap.length field written by the guest driver. PCI config register
reads always produce a 4 byte buffer, so when a driver set cap.length
to 1 for a byte wide access to device_status at common config offset
0x14, the VMM passed all 4 bytes to read_bar, dispatching to the
dword handler which does not cover that offset.

Use cap.length to determine the actual BAR access width per virtio
spec 4.1.4.9.1. Also replace the unsafe transmute with the safe
Le32::to_native() conversion.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-05-23 16:09:07 +02:00
committed by Rob Bradford
parent cc2e528c88
commit feb1c4a2d6

View File

@@ -221,6 +221,14 @@ impl VirtioPciCfgCap {
..Default::default()
}
}
/// Return the BAR offset and clamped access length for a PCI CFG cap
/// indirect BAR access.
fn bar_access_params(&self, data_len: usize) -> (u64, usize) {
let bar_offset = self.cap.offset.to_native() as u64;
let cap_length = self.cap.length.to_native() as usize;
(bar_offset, cmp::min(cap_length, data_len))
}
}
#[derive(Clone, Copy, Default)]
@@ -771,10 +779,10 @@ impl VirtioPciDevice {
.unwrap();
}
} else {
let bar_offset: u32 =
// SAFETY: we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long.
unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) };
self.read_bar(0, bar_offset as u64, data);
let (bar_offset, access_len) = self.cap_pci_cfg_info.cap.bar_access_params(data_len);
if access_len > 0 {
self.read_bar(0, bar_offset, &mut data[..access_len]);
}
}
}
@@ -792,10 +800,12 @@ impl VirtioPciDevice {
right[..data_len].copy_from_slice(data);
None
} else {
let bar_offset: u32 =
// SAFETY: we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long.
unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) };
self.write_bar(0, bar_offset as u64, data)
let (bar_offset, access_len) = self.cap_pci_cfg_info.cap.bar_access_params(data_len);
if access_len > 0 {
self.write_bar(0, bar_offset, &data[..access_len])
} else {
None
}
}
}