Root Cause Analysis
1. Crash Site and Failure Mechanism
The crash occurs in kernel/time/timer.c during softirq timer handling:
run_timer_softirq() $\to$ run_timer_base() $\to$ __run_timers() $\to$ expire_timers() $\to$ detach_timer() $\to$ __hlist_del().
In include/linux/list.h:
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
WRITE_ONCE(*pprev, next);
if (next)
WRITE_ONCE(next->pprev, pprev);
}
When detach_timer() attempts to detach an expired struct timer_list residing at kernel virtual address 0xffff8880456d98d8 (physical address 0x456d98d8), it reads entry->next into R15 and entry->pprev into RAX. Both fields contain 0xffffffffffffffff. Executing WRITE_ONCE(*pprev, next) (mov %r15, (%rax)) attempts a supervisor write to virtual address 0xffffffffffffffff, triggering a fatal kernel page fault (#PF: supervisor write access in kernel mode, address ffffffffffffffff).
2. Root Cause and Trigger
The crash is triggered by proc_bus_pci_write() in drivers/pci/proc.c:
- The reproducer mounts
procfs at ./proc, opens ./proc/bus/pci/00/00.0 with O_RDWR, and writes 125 (0x7d) bytes starting at offset 0 (*ppos = 0).
- Unless kernel lockdown (
LOCKDOWN_PCI_ACCESS) is enforced, proc_bus_pci_write() performs unchecked raw configuration writes via pci_user_write_config_byte(), pci_user_write_config_word(), and pci_user_write_config_dword().
- PCI device
00:00.0 corresponds to Bus 0, Device 0, Function 0, which on QEMU's Q35 platform is the host bridge / MCH (Memory Controller Hub / DRAM controller).
- Writing raw values into configuration space offsets
0x00 through 0x7c of device 00:00.0 reconfigures critical memory-routing and chipset registers (such as PCIEXBAR at offset 0x60, PAM, and memory limit registers like TOLUD).
- As a result of reconfiguring the host bridge registers, QEMU's address space decoding modifies or unmaps physical RAM regions. Subsequent CPU read accesses to affected physical memory addresses (such as
0x456d98d8) return floating-bus all-ones (0xffffffffffffffff).
- When the timer softirq attempts to service and dequeue an active
struct timer_list mapped in that address range, all struct fields read as 0xffffffffffffffff, leading directly to the wild pointer write in __hlist_del().