Repository navigation
Description
- RT-Thread Version: master (v5.4.0-dev, commit 5d1fb66, 2026-10-15); affected since v5.2.0
(introduced by 2168ed8, 2024-05-30, "[DM/Feature] Basic PCI/PCIe bus") - Affected area: Device drivers →
components/drivers/pci/(MSI/MSI-X core,msi/msi.c) - Architecture: ARM64 (any arch with
RT_PCI_MSI; verified on aarch64) - Board / hardware: QEMU
qemu-virt64-aarch64official default.config
(RT_USING_PCI=y,RT_PCI_MSI=y,RT_PCI_ECAM=y,RT_NVME_PCI=y,RT_PIC_ARM_GIC_V2M=y),
stocknvmePCIe device. Any board where a driver enables MSI-X at probe (nvme, ivshmem, virtio-pci) is affected. - Develop Toolchain: arm-gnu-toolchain-13.2.Rel1 + scons; QEMU 9.2.3 (device-side injection via local
QEMU nvme model patch to forge the read-only capability register)
Describe the bug
msix_table_remap() (components/drivers/pci/msi/msi.c:737) reads the device-controlled,
read-only MSI-X Table Offset register (PCIR_MSIX_TABLE, capability offset +4) and uses it without
any validation:
rt_pci_read_config_u32(pdev, pdev->msix_cap + PCIR_MSIX_TABLE, &table_offset);
bir = (rt_uint8_t)(table_offset & PCIM_MSIX_BIR_MASK); /* 0x7 -> bir = 0..7 */
if (pdev->resource[bir].flags & PCI_BUS_REGION_F_NONE) { ... return RT_NULL; }
table_base_phys = pdev->resource[bir].base + (table_offset & ~PCIM_MSIX_BIR_MASK);
return rt_ioremap((void *)table_base_phys, entries_nr * PCIM_MSIX_ENTRY_SIZE);Three validations required by the spec / carried by Linux are missing:
- BIR range: PCI spec allows BIR = 0–5, but
birhere is masked with 0x7 (0–7) and used directly
as an index intopdev->resource[RT_PCI_BAR_NR_MAX = 6]. Withbir = 6the access aliases the
adjacent memberpdev->rom(include/drivers/pci.h:161-162), which is{base:0, size:0, flags:0}
for a device without an option ROM. SincePCI_BUS_REGION_F_MEM == 0, the "invalid BAR" guard
(flags & PCI_BUS_REGION_F_NONE) passes, andtable_base_phys = 0 + (table_offset & ~7)becomes an
absolute, fully attacker-chosen physical address (29 bits — covers all RAM on the reference board). - Table-within-BAR bound: even for
bir = 0..5pointing at a real BAR,table_offset(up to
0xFFFFFFF8) is never checked againstresource[bir].size. - Table size consistency:
entries_nrcomes from a separate read ofPCIR_MSIX_CTRL
(msix_capability_init), so a device returning inconsistent reads makes the final masking loop
(0x80000000per entry, up to 2048 entries = 32 KB) run past the remapped region.
After the bogus rt_ioremap() succeeds (it performs no physical-address validation,
components/mm/ioremap.c:92), MSI-X initialization reads and writes through the mapping:
msix_setup_msi_descs()readsentry+0xc(kernel-memory disclosure path);rt_pci_msi_write_msg()writesmsg.address_lo(GIC MSI doorbell, e.g.0x08020040),
msg.address_hi(0) andmsg.data(SPI number) atentry+0/4/8;- the final loop sprays
0x80000000over every entry'svector_ctrl.
To reproduce (full chain, official image, zero interaction)
Reference: rtthread-ws/audit/RT_pci/exp/ (patch, scripts, GDB and console logs).
- Build stock
bsp/qemu-virt64-aarch64(official default config) — no source change needed. - Control: boot QEMU (
-M virt,its=on,gic-version=2) with a stocknvmedevice → boots to msh,
nvme0n1registers,list_blkworks (control-stock.log). GDB shows the exact chain runs at boot:
pci_nvme_probe → rt_pci_msix_enable → msix_capability_init → msix_table_remap
(gdb-msix-hit.log). - Attack: same command, with the device's read-only
MSI-X Table Offsetregister forged to
0x4023f06e(= phys&rt_interrupt_enter_hook0x4023f068| bir 6) — done with a small local QEMU
nvme-model patch (qemu-nvme-msix-inject.patch, emulating a device whose firmware reports
ill-formatted MSI-X metadata; the real MSI-X table MMIO stays inside BAR0). - Observed (
attack2-gdb.log,attack2-console.log):- GDB watchpoint on the remapped entry: kernel writes
0x08020040(MSI doorbell) into physical
0x4023f068; backtracert_pci_msi_write_msg ← rt_pci_msi_setup_irqs ← msix_capability_init ← rt_pci_msix_enable ← pci_nvme_probe; - physical memory before: all zeros, after: MSI message fields;
- on the next interrupt the corrupted hook is called:
exception info: esr.EC:0x21, epc:0x0000000008020040, Instruction abort from current exception level
in an endless exception loop — control flow hijacked to the value the device planted.
- GDB watchpoint on the remapped entry: kernel writes
Expected behaviour
bir must be range-checked (bir >= RT_PCI_BAR_NR_MAX → reject) and table_offset + entries_nr * 16 must be validated against resource[bir].size before rt_ioremap()
(also reject pdev->rom/unassigned BARs and size == 0).
Actual behaviour
The kernel maps and reads/writes an arbitrary attacker-chosen physical address during boot-time driver
probe, corrupting kernel memory (function-pointer hijack demonstrated; EPC equals the planted value),
then dead-loops in the exception handler.
Other additional context
- Attack prerequisite (explicit): a malicious or compromised PCIe device (e.g. NVMe SSD with
malicious firmware, supply-chain-tampered peripheral, or forged device on an exposed M.2/PCIe slot)
that reports an ill-formatted MSI-X capability. No software privilege or user interaction is needed;
the vulnerable path runs automatically during boot on the official qemu-virt64-aarch64 default config. - Severity: CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 6.8 — the score is capped by the
physical/peripheral vector, but the technical impact is arbitrary kernel physical-memory read/write
at boot, i.e. full kernel compromise from a malicious device. - CWE: CWE-787 (out-of-bounds write) / CWE-129 (improper validation of array index) /
CWE-20 (improper input validation of device-controlled field). - Versions: introduced by 2168ed8 (2024-05-30); first affected release v5.2.0; v5.2.0–v5.3.0
and master (2026-11-05) verified affected; no fix or existing report found on GitHub. - Relation to existing reports: new — not related to any NVD CVE or existing issue (the RT-Thread
PCI framework has had no security audit coverage; [Bug] nvme: kernel heap memory leak per inactive namespace in nvme_scan_device() exhausts system heap and can brick boot #11919 concerns nvme namespace scanning, a
different root cause).
Activity
github-actions commentedon Nov 4, 2026
如需认领并处理此 Issue,请评论
/claim。To claim and work on this issue, comment
/claim.BernardXiong commentedon Nov 4, 2026
已确认,这个问题是真实存在的,感谢报告。
我核对了代码:
msix_table_remap()里的bir = table_offset & 0x7会得到 0~7,但pdev->resource[]只有RT_PCI_BAR_NR_MAX(6)项。bir = 6时索引会落到紧随其后的pdev->rom,无 option ROM 的设备该成员是全 0,flags & PCI_BUS_REGION_F_NONE的判空正好被绕过,于是base + offset变成设备可完全控制的物理地址;叠加 table offset / 长度从未和 BAR size 比较,rt_ioremap()之后就会对任意物理地址读写,报告里的分析链路成立。bir = 7则是直接的数组越界。修复已提交 PR #11986,主要做了四件事:
bir >= RT_ARRAY_SIZE(pdev->resource);PCI_BUS_REGION_F_NONE)或 size 为 0 的 BAR;table_offset + entries_nr * 16必须完整落在 BAR 内;nvec > table_size,避免设备多次读取返回不一致的值。另外补充一点:当前 master 的
qemu-virt64-aarch64默认.config在 c89121a 之后已关闭 PCI,所以默认配置本身不会走到这条路径;但只要启用RT_USING_PCI/RT_PCI_MSI(包括报告验证过的版本),问题就存在,修复仍然必要。已在本地用 xPack aarch64-none-elf-gcc 13.2.1 打开 PCI/MSI 编译
qemu-virt64-aarch64,编译通过,CI 结果会继续跟进。Confirmed — this is a real issue. Thanks for the detailed report.
The analysis holds:
bir = table_offset & 0x7yields 0..7 whilepdev->resource[]has only 6 entries. Withbir = 6the index aliasespdev->rom, which is all-zero when there is no option ROM, so thePCI_BUS_REGION_F_NONEcheck is bypassed andbase + offsetbecomes a fully device-controlled physical address. The table offset and length are also never checked against the BAR size, so the followingrt_ioremap()gives read/write access to that address.bir = 7is an out-of-bounds array access as well.Fix is in PR #11986:
bir >= RT_ARRAY_SIZE(pdev->resource);PCI_BUS_REGION_F_NONE) or zero-sized BARs;table_offset + entries_nr * 16is fully contained in the BAR;nvec > table_size, so inconsistent device reads cannot exceed the mapped table.One note: on current master the default
qemu-virt64-aarch64.configdisables PCI (since c89121a), so the default config no longer reaches this path. The bug is still reachable wheneverRT_USING_PCI/RT_PCI_MSIis enabled, including the versions verified in the report.Verified locally by building
qemu-virt64-aarch64with PCI/MSI enabled using xPack aarch64-none-elf-gcc 13.2.1. I will keep an eye on CI.[DM/PCI] Validate MSI-X table BIR and offset against BAR (RT-Thread#1…
[DM/PCI] Validate MSI-X table BIR and offset against BAR (RT-Thread#1…