From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793971512; b=GH4kwW2ws/wcdtr1/b/x00KvnamABkUUSNQovkWh8xF8R1onQvHkvcaBO2gSszFCEsov uB/zycVHVBjLWbQE9jfrV0eZWbJHsizIYvMuyx80mxvlGVqdmVFS9pT4E+wFGewokhIPZ BhjW7EgFpqkRqhPzPz/GHZJifGUYbqARbyldmisVDKn1F57elVx06nhiDF9qZ8a3vXROm M95Nvr3J6YWWyTNnSPcqpQ4LIkLbmo8OxDAUU7m2BbbezsvYmHsDdgT0lrpQa/bsfmn9y UGhttyQub5Tacu9SW8ly/FvnsGh/63GSZl3fsYKCQ0Cg4AVACT3MwSzzAKO/P3fY90A== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793971512; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:Content-Transfer-Encoding; bh=z/haA5YvmE2PsqyjlqDInHFKwOpvKZy7p1qJhANsFBI=; b=vksCtZSEqzQ5OchAhl7LXqHhvZfcqyEW+vusg7ecWxLHGzg/avQFz9Ok70lXxbqLv5NP QlN53GaPWq+PujMNoMIcM5gaJ/+0ae2keja8ii/SNaOVVE9tWH6WXgUahAGbjTXGqd38r nzpRm6IgmdrswskhCKlYCyskv/mroTawq8jDXT8rxGSuWv72AYsRBwXwzl/CLX3owwzsN rgqTQK2J9ZjGyNydm6gEOaSu1tFcE66INDucs28S3CLgbenjWOhdkCSHH8OlUhRy2LXYz Icf0YCmMLxnqVaeChvBXQBkwsQVfqN7kUoKTfCQrJ6GbE83nbiAA1ZcDLpGQK9ANupQ== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=U9vFLjHr; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C8D30606A326 for ; Fri, 6 Nov 2026 13:25:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=U9vFLjHr Received: by smtp.kernel.org (Postfix) with ESMTPSA id E28301F0089F; Fri, 6 Nov 2026 13:25:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1793971511; bh=z/haA5YvmE2PsqyjlqDInHFKwOpvKZy7p1qJhANsFBI=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=U9vFLjHrQ4tVIXUx5/vdViKDKSg3t2QALbzOoXfiI+IxVaCzF1QQUGUTLmGCoK5Xo T1aWc8ba9G6GF7l7Fpdz6gLrRx4yLPEih0fWRsgVATOpzJ75wnasQSB1mxD276Gk9Z FOt3Rv9R/BgpR/gU4vUVsBe9M7mGY6xRiyAHmWlDC+B5QGE8quhi9Xu60ecvU2MCjG DDbS58KWGWtwE848v1NN2IVrwvJEgJwpKAGj6942mMC15jaKufstc/MJ/xnr7Ek2o/ a46UJQxiZN8jijJnvc9orzbrLxNaIIzY0sPgsI4+V3by3JLUU3BmsRVYwfqzJMj9fR 0fXL2lRepopXw== From: Sasha Levin To: stable@vger.kernel.org Cc: Keqiang Duan , Qinguang Chen , Zhiping Du , Chao Gao , Sean Christopherson , Sasha Levin Subject: [PATCH 6.12.y] KVM: x86: Clear hardware HLT state when userspace makes a vCPU not-halted Date: Fri, 6 Nov 2026 08:25:09 -0500 Message-ID: <20261106132303.74970-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <2026110432-postcard-cryptic-2e28@gregkh> References: <2026110432-postcard-cryptic-2e28@gregkh> X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Keqiang Duan [ Upstream commit 883719f57f5d3898637b130d6db4742471cad618 ] Force a vCPU out of its hardware-tracked halted state when userspace puts the vCPU into any state other than HALTED via KVM_SET_MP_STATE, i.e. clear VMCS.GUEST_ACTIVITY_STATE if it says the vCPU is halted. Add an optional kvm_x86_ops hook to do the clearing, as SVM has no equivalent VMCB field. When HLT-exiting is disabled for a VM (KVM_CAP_X86_DISABLE_EXITS with KVM_X86_DISABLE_EXITS_HLT, e.g. QEMU's "-overcommit cpu-pm=on"), a guest HLT halts the physical CPU instead of exiting to KVM, and hardware saves GUEST_ACTIVITY_STATE=HLT into the VMCS on the next VM-Exit. That field is sticky: it survives VM-Exit/VM-Enter and is only cleared by vmx_clear_hlt() on event injection, or by vmx_vcpu_reset() on INIT / vCPU creation. Nothing clears it on a userspace-driven state change. KVM_SET_REGS only writes the software register cache and KVM_SET_MP_STATE only writes vcpu->arch.mp_state; kvm_vcpu_running() likewise consults software state only. A VMM that emulates a machine reset therefore ends up with a vCPU that KVM happily VM-Enters while hardware refuses to fetch instructions. Reproduce with a Linux guest by triggering a panic/kdump on a non-boot vCPU: nmi_shootdown_cpus() parks the other vCPUs -- including vCPU0 -- in crash_nmi_callback(), which does local_irq_disable() followed by a bare HLT. The capture kernel then resets the machine via port 0xCF9. QEMU rewrites RIP to 0xfff0 and sets mp_state to RUNNABLE, but vCPU0's GUEST_ACTIVITY_STATE is still HLT, so the BSP never executes the reset vector, never sends SIPIs, and the entire VM hangs at "reboot: machine restart" forever. Only destroying and recreating the VM recovers it. Key off "not HALTED" rather than "is RUNNABLE" as HALTED is the only target state for which retaining the hardware halted state is meaningful; every other transition either can't be reached on VMX, e.g. AP_RESET_HOLD, or goes through vmx_vcpu_reset(), which stuffs GUEST_ACTIVITY_ACTIVE anyways. This also captures the intent more directly: KVM only ever drops the hardware halted state, it never installs it. Hook KVM_SET_MP_STATE rather than the VM-Enter path so that the clearing is driven by an explicit userspace declaration, and so that no work is added to vmx_vcpu_run(). Gate the call on kvm_hlt_in_guest() even though vmx_clear_hlt() checks it as well, as the check is cheap and makes it obvious in common code when the hardware state can exist at all. Do nothing for TDX vCPUs as TDX doesn't support disabling HLT-exiting and KVM cannot access a TD's VMCS, i.e. the VMCS used to run the vCPU can't in HALTED state, and even if it were, KVM can't change it. Fixes: caa057a2cad6 ("KVM: X86: Provide a capability to disable HLT intercepts") Cc: stable@vger.kernel.org Tested-by: Qinguang Chen Signed-off-by: Zhiping Du Signed-off-by: Keqiang Duan Reviewed-by: Chao Gao Link: https://patch.msgid.link/20260820123512.87236-1-duankeqiangcym@gmail.com [sean: drop claim that spurious wakeups are ok, massage changelog] Signed-off-by: Sean Christopherson [ sashal: Adapted to direct VMX callback registration and existing MP-state assignments because the TDX wrapper and kvm_set_mp_state() helper are absent. ] Signed-off-by: Sasha Levin --- arch/x86/include/asm/kvm-x86-ops.h | 1 + arch/x86/include/asm/kvm_host.h | 7 +++++++ arch/x86/kvm/vmx/main.c | 1 + arch/x86/kvm/vmx/vmx.c | 2 +- arch/x86/kvm/vmx/x86_ops.h | 1 + arch/x86/kvm/x86.c | 13 +++++++++++++ 6 files changed, 24 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h index 861d080ed4c6a..63bdbd15efcde 100644 --- a/arch/x86/include/asm/kvm-x86-ops.h +++ b/arch/x86/include/asm/kvm-x86-ops.h @@ -78,6 +78,7 @@ KVM_X86_OP(interrupt_allowed) KVM_X86_OP(nmi_allowed) KVM_X86_OP(get_nmi_mask) KVM_X86_OP(set_nmi_mask) +KVM_X86_OP_OPTIONAL(clear_hlt) KVM_X86_OP(enable_nmi_window) KVM_X86_OP(enable_irq_window) KVM_X86_OP_OPTIONAL(update_cr8_intercept) diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index cec497aed149a..93d3f8010a1f8 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -1735,6 +1735,13 @@ struct kvm_x86_ops { int (*nmi_allowed)(struct kvm_vcpu *vcpu, bool for_injection); bool (*get_nmi_mask)(struct kvm_vcpu *vcpu); void (*set_nmi_mask)(struct kvm_vcpu *vcpu, bool masked); + /* + * Force the vCPU out of any hardware-tracked halted/inactive state so + * that it will fetch and execute instructions on the next VM-Enter. + * Only needed by VMX, where VMCS.GUEST_ACTIVITY_STATE persists across + * VM-Exit/VM-Enter; SVM has no equivalent VMCB field. + */ + void (*clear_hlt)(struct kvm_vcpu *vcpu); /* Whether or not a virtual NMI is pending in hardware. */ bool (*is_vnmi_pending)(struct kvm_vcpu *vcpu); /* diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c index 3f83e36a657b9..8a922ce0d19c4 100644 --- a/arch/x86/kvm/vmx/main.c +++ b/arch/x86/kvm/vmx/main.c @@ -90,6 +90,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = { .nmi_allowed = vmx_nmi_allowed, .get_nmi_mask = vmx_get_nmi_mask, .set_nmi_mask = vmx_set_nmi_mask, + .clear_hlt = vmx_clear_hlt, .enable_nmi_window = vmx_enable_nmi_window, .enable_irq_window = vmx_enable_irq_window, .update_cr8_intercept = vmx_update_cr8_intercept, diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index a36d9f504ba53..8e23e901a7593 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -1814,7 +1814,7 @@ int vmx_skip_emulated_instruction(struct kvm_vcpu *vcpu) return skip_emulated_instruction(vcpu); } -static void vmx_clear_hlt(struct kvm_vcpu *vcpu) +void vmx_clear_hlt(struct kvm_vcpu *vcpu) { /* * Ensure that we clear the HLT state in the VMCS. We don't need to diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h index 5e4ce13ab3051..4348674de9f35 100644 --- a/arch/x86/kvm/vmx/x86_ops.h +++ b/arch/x86/kvm/vmx/x86_ops.h @@ -96,6 +96,7 @@ int vmx_interrupt_allowed(struct kvm_vcpu *vcpu, bool for_injection); int vmx_nmi_allowed(struct kvm_vcpu *vcpu, bool for_injection); bool vmx_get_nmi_mask(struct kvm_vcpu *vcpu); void vmx_set_nmi_mask(struct kvm_vcpu *vcpu, bool masked); +void vmx_clear_hlt(struct kvm_vcpu *vcpu); void vmx_enable_nmi_window(struct kvm_vcpu *vcpu); void vmx_enable_irq_window(struct kvm_vcpu *vcpu); void vmx_update_cr8_intercept(struct kvm_vcpu *vcpu, int tpr, int irr); diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 68599667907c8..ea8b6ec561dc4 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -11281,6 +11281,19 @@ int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu, set_bit(KVM_APIC_SIPI, &vcpu->arch.apic->pending_events); } else vcpu->arch.mp_state = mp_state->mp_state; + + /* + * Force the vCPU out of any hardware-tracked halted state, e.g. VMX's + * GUEST_ACTIVITY_STATE=HLT, when userspace puts the vCPU into a state + * other than HALTED. The hardware state is sticky across VM-Exit and + * VM-Enter and is not touched by any other ioctl, so a vCPU that halted + * with HLT-exiting disabled stays wedged even after userspace rewrites + * its registers, e.g. when a VMM emulates a machine reset. + */ + if (kvm_hlt_in_guest(vcpu->kvm) && + mp_state->mp_state != KVM_MP_STATE_HALTED) + kvm_x86_call(clear_hlt)(vcpu); + kvm_make_request(KVM_REQ_EVENT, vcpu); ret = 0; -- 2.53.0