When running a PREEMPT_RT kernel, an attempt to acquire `gpc->lock` via `read_lock_irqsave()` in `kvm_xen_set_evtchn_fast()` can lead to a "sleeping function called from invalid context" BUG. This happens because `gpc->lock` is an `rwlock_t`, which is mapped to a sleeping lock (`rt_rw_lock`) on PREEMPT_RT. However, `kvm_xen_set_evtchn_fast()` can be called from hardirq or atomic contexts, such as `xen_timer_callback()` (an hrtimer callback) or `kvm_arch_set_irq_inatomic()` (during irqfd waitqueue wakeups). BUG: sleeping function called from invalid context at kernel/locking/spinlock_rt.c:248 in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 5685, name: kworker/0:5 ... Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 __might_resched+0x329/0x480 kernel/sched/core.c:9197 rt_read_lock+0xa9/0x4b0 kernel/locking/spinlock_rt.c:248 kvm_xen_set_evtchn_fast+0x1f4/0x990 arch/x86/kvm/xen.c:1822 xen_timer_callback+0x109/0x220 arch/x86/kvm/xen.c:140 __run_hrtimer kernel/time/hrtimer.c:2032 [inline] __hrtimer_run_queues+0x3a0/0xaf0 kernel/time/hrtimer.c:2096 hrtimer_interrupt+0x44a/0x900 kernel/time/hrtimer.c:2215 local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1051 [inline] __sysvec_apic_timer_interrupt+0x102/0x430 arch/x86/kernel/apic/apic.c:1068 sysvec_apic_timer_interrupt+0xa1/0xc0 arch/x86/kernel/apic/apic.c:1062 To fix this, modify `kvm_xen_set_evtchn_fast()` to accept a `bool in_atomic` parameter. When called from an atomic context, use `read_trylock()` instead of `read_lock_irqsave()`. If the trylock fails, safely fall back to the slow path (returning `-EWOULDBLOCK` or kicking the vCPU), which the callers already expect and handle. Update all callers to pass the appropriate context flag. Fixes: 14243b387137 ("KVM: x86/xen: Add KVM_IRQ_ROUTING_XEN_EVTCHN and event channel delivery") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+e42793f1299e53beb2ee@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e42793f1299e53beb2ee Link: https://syzkaller.appspot.com/ai_job?id=f4bc5243-4ceb-4399-a79c-4c7ff19fbe0f To: "Borislav Petkov" To: "Dave Hansen" To: "David Woodhouse" To: To: "Ingo Molnar" To: "Paul Durrant" To: "Paolo Bonzini" To: "Sean Christopherson" To: "Thomas Gleixner" To: To: "David Woodhouse" Cc: "H. Peter Anvin" Cc: --- diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c index cb8ac4b9b..9aef11529 100644 --- a/arch/x86/kvm/irq.c +++ b/arch/x86/kvm/irq.c @@ -267,7 +267,7 @@ int kvm_arch_set_irq_inatomic(struct kvm_kernel_irq_routing_entry *e, if (!level) return -1; - return kvm_xen_set_evtchn_fast(&e->xen_evtchn, kvm); + return kvm_xen_set_evtchn_fast(&e->xen_evtchn, kvm, true); #endif default: break; diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c index 694b31c1f..14988ae71 100644 --- a/arch/x86/kvm/xen.c +++ b/arch/x86/kvm/xen.c @@ -137,7 +137,7 @@ static enum hrtimer_restart xen_timer_callback(struct hrtimer *timer) e.port = vcpu->arch.xen.timer_virq; e.priority = KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL; - rc = kvm_xen_set_evtchn_fast(&e, vcpu->kvm); + rc = kvm_xen_set_evtchn_fast(&e, vcpu->kvm, true); if (rc != -EWOULDBLOCK) { vcpu->arch.xen.timer_expires = 0; return HRTIMER_NORESTART; @@ -1792,7 +1792,7 @@ static void kvm_xen_check_poller(struct kvm_vcpu *vcpu, int port) * It is also called directly from kvm_arch_set_irq_inatomic(), where the * only check on its return value is a comparison with -EWOULDBLOCK'. */ -int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, struct kvm *kvm) +int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, struct kvm *kvm, bool in_atomic) { struct gfn_to_pfn_cache *gpc = &kvm->arch.xen.shinfo_cache; struct kvm_vcpu *vcpu; @@ -1819,7 +1819,15 @@ int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, struct kvm *kvm) idx = srcu_read_lock(&kvm->srcu); - read_lock_irqsave(&gpc->lock, flags); + if (in_atomic) { + local_irq_save(flags); + if (!read_trylock(&gpc->lock)) { + local_irq_restore(flags); + goto out_rcu_no_lock; + } + } else { + read_lock_irqsave(&gpc->lock, flags); + } if (!kvm_gpc_check(gpc, PAGE_SIZE)) goto out_rcu; @@ -1853,7 +1861,18 @@ int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, struct kvm *kvm) read_unlock_irqrestore(&gpc->lock, flags); gpc = &vcpu->arch.xen.vcpu_info_cache; - read_lock_irqsave(&gpc->lock, flags); + if (in_atomic) { + local_irq_save(flags); + if (!read_trylock(&gpc->lock)) { + local_irq_restore(flags); + if (!test_and_set_bit(port_word_bit, + &vcpu->arch.xen.evtchn_pending_sel)) + kick_vcpu = true; + goto out_rcu_no_lock; + } + } else { + read_lock_irqsave(&gpc->lock, flags); + } if (!kvm_gpc_check(gpc, sizeof(struct vcpu_info))) { /* * Could not access the vcpu_info. Set the bit in-kernel @@ -1888,6 +1907,7 @@ int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, struct kvm *kvm) out_rcu: read_unlock_irqrestore(&gpc->lock, flags); + out_rcu_no_lock: srcu_read_unlock(&kvm->srcu, idx); if (kick_vcpu) { @@ -1903,7 +1923,7 @@ static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm) bool mm_borrowed = false; int rc; - rc = kvm_xen_set_evtchn_fast(xe, kvm); + rc = kvm_xen_set_evtchn_fast(xe, kvm, false); if (rc != -EWOULDBLOCK) return rc; @@ -1937,7 +1957,7 @@ static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm) struct gfn_to_pfn_cache *gpc = &kvm->arch.xen.shinfo_cache; int idx; - rc = kvm_xen_set_evtchn_fast(xe, kvm); + rc = kvm_xen_set_evtchn_fast(xe, kvm, false); if (rc != -EWOULDBLOCK) break; diff --git a/arch/x86/kvm/xen.h b/arch/x86/kvm/xen.h index 59e6128a7..13d34a06f 100644 --- a/arch/x86/kvm/xen.h +++ b/arch/x86/kvm/xen.h @@ -32,7 +32,7 @@ void kvm_xen_destroy_vm(struct kvm *kvm); void kvm_xen_init_vcpu(struct kvm_vcpu *vcpu); void kvm_xen_destroy_vcpu(struct kvm_vcpu *vcpu); int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, - struct kvm *kvm); + struct kvm *kvm, bool in_atomic); int kvm_xen_setup_evtchn(struct kvm *kvm, struct kvm_kernel_irq_routing_entry *e, const struct kvm_irq_routing_entry *ue); base-commit: db2ddb87143519e20a95aa36c60b36107b736a58 -- This is an AI-generated patch subject to moderation. Reply with '#syz upstream' to Sign-off the patch as a human author and send it to the upstream kernel mailing lists. Reply with '#syz reject' to reject it ('#syz unreject' to undo). See https://goo.gle/syzbot-ai-patches for information about AI-generated patches. You can comment on the patch as usual, syzbot will try to address the comments and send a new version of the patch if necessary. syzbot engineers can be reached at syzkaller@googlegroups.com.