kvm_ioapic_eoi_inject_work() re-delivers a throttled level-triggered interrupt via kvm_irq_delivery_to_apic(), which walks kvm->arch.apic_map and dereferences the destination vCPU's APIC. The work is cancelled only in kvm_ioapic_destroy(), which runs after kvm_destroy_vcpus() has freed the vCPUs and their APICs. kvm_free_lapic() does not rebuild apic_map, so the map is left with dangling pointers, and a work item that fires during that window reads freed memory: BUG: KASAN: slab-use-after-free in __kvm_irq_delivery_to_apic_fast (arch/x86/kvm/lapic.c:1248) Read of size 8 by task kworker/3:1 Workqueue: events kvm_ioapic_eoi_inject_work __kvm_irq_delivery_to_apic_fast (arch/x86/kvm/lapic.c:1248) __kvm_irq_delivery_to_apic (arch/x86/kvm/lapic.c:1343) ioapic_service (arch/x86/kvm/ioapic.c:492) kvm_ioapic_eoi_inject_work (arch/x86/kvm/ioapic.c:525) process_one_work Freed by task 153: kvm_arch_vcpu_destroy (arch/x86/kvm/x86.c:12871) kvm_destroy_vcpus (virt/kvm/kvm_main.c:489) kvm_arch_destroy_vm (arch/x86/kvm/x86.c:13402) kvm_destroy_vm (virt/kvm/kvm_main.c:1302) kvm_vm_release (virt/kvm/kvm_main.c:1363) A guest arms the work by EOIing a level-triggered pin 10000 times in a row, so the window is reachable from guest ring 0 whenever its VM is torn down soon after. Add kvm_ioapic_pre_destroy() and call it from kvm_arch_pre_destroy_vm(), which already stops the PIT and the kvmclock/MMU workers before vCPUs are freed for the same reason. Fixes: 184564efae4d ("kvm: ioapic: conditionally delay irq delivery duringeoi broadcast") Reported-by: Zhong Wang Reported-by: Xuanqing Shi Signed-off-by: Weiming Shi --- arch/x86/kvm/ioapic.c | 8 ++++++++ arch/x86/kvm/ioapic.h | 1 + arch/x86/kvm/x86.c | 1 + 3 files changed, 10 insertions(+) diff --git a/arch/x86/kvm/ioapic.c b/arch/x86/kvm/ioapic.c index eed96ff6e722..51b7b0807bb6 100644 --- a/arch/x86/kvm/ioapic.c +++ b/arch/x86/kvm/ioapic.c @@ -740,6 +740,14 @@ int kvm_ioapic_init(struct kvm *kvm) return ret; } +void kvm_ioapic_pre_destroy(struct kvm *kvm) +{ + struct kvm_ioapic *ioapic = kvm->arch.vioapic; + + if (ioapic) + cancel_delayed_work_sync(&ioapic->eoi_inject); +} + void kvm_ioapic_destroy(struct kvm *kvm) { struct kvm_ioapic *ioapic = kvm->arch.vioapic; diff --git a/arch/x86/kvm/ioapic.h b/arch/x86/kvm/ioapic.h index 3dadae093690..5bfbae63f368 100644 --- a/arch/x86/kvm/ioapic.h +++ b/arch/x86/kvm/ioapic.h @@ -105,6 +105,7 @@ void kvm_rtc_eoi_tracking_restore_one(struct kvm_vcpu *vcpu); void kvm_ioapic_update_eoi(struct kvm_vcpu *vcpu, int vector, int trigger_mode); int kvm_ioapic_init(struct kvm *kvm); +void kvm_ioapic_pre_destroy(struct kvm *kvm); void kvm_ioapic_destroy(struct kvm *kvm); int kvm_ioapic_set_irq(struct kvm_kernel_irq_routing_entry *e, struct kvm *kvm, int irq_source_id, int level, bool line_status); diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index fd1c4a36b593..3c03fd75c93b 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -13374,6 +13374,7 @@ void kvm_arch_pre_destroy_vm(struct kvm *kvm) */ #ifdef CONFIG_KVM_IOAPIC kvm_free_pit(kvm); + kvm_ioapic_pre_destroy(kvm); #endif kvm_mmu_pre_destroy_vm(kvm); -- 2.54.0