Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

KVM: LAPIC: Fix reentrancy issues with preempt notifiers

Preempt can occur in the preemption timer expiration handler:

CPU0 CPU1

preemption timer vmexit
handle_preemption_timer(vCPU0)
kvm_lapic_expired_hv_timer
hv_timer_is_use == true
sched_out
sched_in
kvm_arch_vcpu_load
kvm_lapic_restart_hv_timer
restart_apic_timer
start_hv_timer
already-expired timer or sw timer triggerd in the window
start_sw_timer
cancel_hv_timer
/* back in kvm_lapic_expired_hv_timer */
cancel_hv_timer
WARN_ON(!apic->lapic_timer.hv_timer_in_use); ==> Oops

This can be reproduced if CONFIG_PREEMPT is enabled.

------------[ cut here ]------------
WARNING: CPU: 4 PID: 2972 at /home/kernel/linux/arch/x86/kvm//lapic.c:1563 kvm_lapic_expired_hv_timer+0x9e/0xb0 [kvm]
CPU: 4 PID: 2972 Comm: qemu-system-x86 Tainted: G OE 4.13.0-rc2+ #16
RIP: 0010:kvm_lapic_expired_hv_timer+0x9e/0xb0 [kvm]
Call Trace:
handle_preemption_timer+0xe/0x20 [kvm_intel]
vmx_handle_exit+0xb8/0xd70 [kvm_intel]
kvm_arch_vcpu_ioctl_run+0xdd1/0x1be0 [kvm]
? kvm_arch_vcpu_load+0x47/0x230 [kvm]
? kvm_arch_vcpu_load+0x62/0x230 [kvm]
kvm_vcpu_ioctl+0x340/0x700 [kvm]
? kvm_vcpu_ioctl+0x340/0x700 [kvm]
? __fget+0xfc/0x210
do_vfs_ioctl+0xa4/0x6a0
? __fget+0x11d/0x210
SyS_ioctl+0x79/0x90
do_syscall_64+0x81/0x220
entry_SYSCALL64_slow_path+0x25/0x25
------------[ cut here ]------------
WARNING: CPU: 4 PID: 2972 at /home/kernel/linux/arch/x86/kvm//lapic.c:1498 cancel_hv_timer.isra.40+0x4f/0x60 [kvm]
CPU: 4 PID: 2972 Comm: qemu-system-x86 Tainted: G W OE 4.13.0-rc2+ #16
RIP: 0010:cancel_hv_timer.isra.40+0x4f/0x60 [kvm]
Call Trace:
kvm_lapic_expired_hv_timer+0x3e/0xb0 [kvm]
handle_preemption_timer+0xe/0x20 [kvm_intel]
vmx_handle_exit+0xb8/0xd70 [kvm_intel]
kvm_arch_vcpu_ioctl_run+0xdd1/0x1be0 [kvm]
? kvm_arch_vcpu_load+0x47/0x230 [kvm]
? kvm_arch_vcpu_load+0x62/0x230 [kvm]
kvm_vcpu_ioctl+0x340/0x700 [kvm]
? kvm_vcpu_ioctl+0x340/0x700 [kvm]
? __fget+0xfc/0x210
do_vfs_ioctl+0xa4/0x6a0
? __fget+0x11d/0x210
SyS_ioctl+0x79/0x90
do_syscall_64+0x81/0x220
entry_SYSCALL64_slow_path+0x25/0x25

This patch fixes it by making the caller of cancel_hv_timer, start_hv_timer
and start_sw_timer be in preemption-disabled regions, which trivially
avoid any reentrancy issue with preempt notifier.

Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Radim Krčmář <rkrcmar@redhat.com>
Signed-off-by: Wanpeng Li <wanpeng.li@hotmail.com>
[Add more WARNs. - Paolo]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

authored by

Wanpeng Li and committed by
Paolo Bonzini
1d518c68 67fbcd62

+14 -3
+14 -3
arch/x86/kvm/lapic.c
··· 1495 1495 1496 1496 static void cancel_hv_timer(struct kvm_lapic *apic) 1497 1497 { 1498 + WARN_ON(preemptible()); 1498 1499 WARN_ON(!apic->lapic_timer.hv_timer_in_use); 1499 - preempt_disable(); 1500 1500 kvm_x86_ops->cancel_hv_timer(apic->vcpu); 1501 1501 apic->lapic_timer.hv_timer_in_use = false; 1502 - preempt_enable(); 1503 1502 } 1504 1503 1505 1504 static bool start_hv_timer(struct kvm_lapic *apic) ··· 1506 1507 struct kvm_timer *ktimer = &apic->lapic_timer; 1507 1508 int r; 1508 1509 1510 + WARN_ON(preemptible()); 1509 1511 if (!kvm_x86_ops->set_hv_timer) 1510 1512 return false; 1511 1513 ··· 1538 1538 static void start_sw_timer(struct kvm_lapic *apic) 1539 1539 { 1540 1540 struct kvm_timer *ktimer = &apic->lapic_timer; 1541 + 1542 + WARN_ON(preemptible()); 1541 1543 if (apic->lapic_timer.hv_timer_in_use) 1542 1544 cancel_hv_timer(apic); 1543 1545 if (!apic_lvtt_period(apic) && atomic_read(&ktimer->pending)) ··· 1554 1552 1555 1553 static void restart_apic_timer(struct kvm_lapic *apic) 1556 1554 { 1555 + preempt_disable(); 1557 1556 if (!start_hv_timer(apic)) 1558 1557 start_sw_timer(apic); 1558 + preempt_enable(); 1559 1559 } 1560 1560 1561 1561 void kvm_lapic_expired_hv_timer(struct kvm_vcpu *vcpu) 1562 1562 { 1563 1563 struct kvm_lapic *apic = vcpu->arch.apic; 1564 1564 1565 - WARN_ON(!apic->lapic_timer.hv_timer_in_use); 1565 + preempt_disable(); 1566 + /* If the preempt notifier has already run, it also called apic_timer_expired */ 1567 + if (!apic->lapic_timer.hv_timer_in_use) 1568 + goto out; 1566 1569 WARN_ON(swait_active(&vcpu->wq)); 1567 1570 cancel_hv_timer(apic); 1568 1571 apic_timer_expired(apic); ··· 1576 1569 advance_periodic_target_expiration(apic); 1577 1570 restart_apic_timer(apic); 1578 1571 } 1572 + out: 1573 + preempt_enable(); 1579 1574 } 1580 1575 EXPORT_SYMBOL_GPL(kvm_lapic_expired_hv_timer); 1581 1576 ··· 1591 1582 { 1592 1583 struct kvm_lapic *apic = vcpu->arch.apic; 1593 1584 1585 + preempt_disable(); 1594 1586 /* Possibly the TSC deadline timer is not enabled yet */ 1595 1587 if (apic->lapic_timer.hv_timer_in_use) 1596 1588 start_sw_timer(apic); 1589 + preempt_enable(); 1597 1590 } 1598 1591 EXPORT_SYMBOL_GPL(kvm_lapic_switch_to_sw_timer); 1599 1592