From: David Woodhouse <[email protected]> Windows VSM boot stalls and PatchGuard-induced triple faults traced to missing host behaviours, all confirmed against the reference VSM host:
- A pending interrupt for a suspended higher VTL must preempt the lower VTL. In userspace-switch mode, report it as a new KVM_EXIT_HYPERV_VTL_INTERRUPT exit — from the halted/block path too, since a halted lower VTL never reaches vcpu_enter_guest() — so userspace enters the higher VTL itself and its view of the active VTL stays in sync. kvm_higher_plane_with_event() tests raw pending-ness (kvm_apic_has_interrupt), because a suspended plane carries a stale apicv_active that makes kvm_cpu_has_injectable_intr() report false. - The in-kernel forced entry, like the in-kernel VTL switch, writes the entered VTL's control area through the *entered* plane's context, since the assist page may be protected against the lower VTL; on failure a VTL call forwards to userspace and a forced entry stays pending. The refusal conditions live in kvm_hv_vtl_entry_blocked(), which also gates kvm_higher_plane_with_event() in in-kernel mode: an armed event on a plane that cannot be entered (never run, stopped by userspace, parked on a non-hypercall completion owning the shared run union) must not keep waking a halted lower VTL that has no way to consume it. A plane which has never entered the guest holds reset state and userspace owns the HvCallEnableVpVtl tracking (the reference host fails such calls with HV_STATUS_INVALID_VP_STATE), so hand it the switch and leave the event pending. The resume value is guest-derived in every switch flavour, so KVM_HV_HCALL_RESULT_RAISE_UD is masked out of it, and a target with no completion armed receives it directly in RAX, as the TLFS register-restore contract on VTL return requires. - RAX/RCX of the lower VTL are captured at in-kernel VTL entry and restored on an assist-less, non-fast return; vtl_entry_regs_valid tracks whether the capture is live. A return finding it clear (the entry was performed by userspace, or predates a migration — the capture has no GET/SET exposure) is forwarded to userspace, which owns restoring the lower VTL's registers. - plane_user_stopped is a pure userspace-policy latch: only KVM_SET_PLANE_RUN_STATE writes it, and no runnable plane can carry it (it is cleared at the single RUNNABLE choke point). In-kernel suspensions get their own plane_kernel_stopped flag, reported and restorable as KVM_PLANE_RUN_STATE_KERNEL_STOPPED, with the same plane-0 event gating. The in-kernel VTL switch and the forced entry refuse a user-stopped target (forwarding the switch to userspace or leaving the event pending), and the kvm_vcpu_select_plane() fallback refuses a user-stopped plane 0: KVM_RUN fails with -EINVAL, surfacing the VMM's ordering bug instead of resuming a plane the VMM parked mid-hypercall. - The secure kernel arms HvRegisterCrInterceptControl descriptor-table write intercepts for VTL0, then PatchGuard runs its protected patching window under an intercepted lidt. The write must *not* be applied — the window would execute with a genuinely null IDT and the first event triple-faults the guest. Add a per-vCPU KVM_CAP_HYPERV_REG_INTERCEPT that arms the control word, trap the descriptor-table instructions (SECONDARY_EXEC_DESC, dropped again when the intercepts disarm), and divert armed writes into a KVM_EXIT_HYPERV_REG_INTERCEPT exit with RIP rewound to the intercepted instruction: the higher VTL owns completion, and Windows' patch-protection probe matches its reflected #GP(0) against the probe RIP. - HvCallSendSyntheticClusterIpi(Ex) honours the input's in_vtl field (both the memory and fast forms carry it after the vector), delivering to the resolved plane's twins; reserved bits 7:5 are rejected with HV_STATUS_INVALID_HYPERCALL_INPUT before anything is delivered, as on the reference host. - hv_inkernel_vtl_switch is written under kvm->lock but read locklessly from vCPU context; the accesses carry WRITE_ONCE/READ_ONCE so each individual check reads a coherent value. A VMM that flips the cap while vCPUs run owns the consequences of catching a transition in flight. Also includes debug instrumentation, to be stripped before posting. Signed-off-by: David Woodhouse <[email protected]> Assisted-by: LLM --- Documentation/virt/kvm/api.rst | 31 ++-- arch/x86/hyperv/hv_apic.c | 3 +- arch/x86/include/asm/kvm_host.h | 21 ++- arch/x86/kvm/hyperv.c | 257 +++++++++++++++++++++++++++++--- arch/x86/kvm/hyperv.h | 3 + arch/x86/kvm/vmx/vmx.c | 23 ++- arch/x86/kvm/x86.c | 210 +++++++++++++++++++++++++- include/hyperv/hvgdk_mini.h | 3 +- include/hyperv/hvhdk_mini.h | 3 +- include/linux/kvm_host.h | 12 ++ include/uapi/linux/kvm.h | 13 ++ virt/kvm/kvm_main.c | 35 ++++- 12 files changed, 571 insertions(+), 43 deletions(-) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index c9283994014a..3ef57b3b050b 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -6628,19 +6628,32 @@ a vCPU fd of every plane, not just plane 0. :Returns: 0 on success, <0 on error Reads or sets the per-plane run state of a vCPU: -KVM_PLANE_RUN_STATE_STOPPED (0) or KVM_PLANE_RUN_STATE_RUNNABLE (1). +KVM_PLANE_RUN_STATE_STOPPED (0), KVM_PLANE_RUN_STATE_RUNNABLE (1) or +KVM_PLANE_RUN_STATE_KERNEL_STOPPED (2). When a vCPU thread calls KVM_RUN, KVM executes the lowest-numbered plane -whose vCPU is runnable, falling back to plane 0. A VMM implementing a -software privilege model (such as Hyper-V VSM) switches planes by -marking the target plane's vCPU runnable and the current one stopped -before re-entering KVM_RUN. Pending events for plane 0 make it runnable -again on their own only as long as userspace has not explicitly stopped -it with this ioctl; a VMM-stopped plane 0 keeps its events pending until -the VMM makes it runnable, so a software privilege model can keep a -higher plane running undisturbed. The ioctl is accepted on any plane's +whose vCPU is runnable, falling back to plane 0. If no plane is +runnable and userspace has stopped plane 0, KVM_RUN fails with -EINVAL +instead of overriding the stop. A VMM implementing a software privilege +model (such as Hyper-V VSM) switches planes by marking the target +plane's vCPU runnable and the current one stopped before re-entering +KVM_RUN. Pending events for plane 0 make it runnable again on their own +only as long as userspace has not explicitly stopped it with this ioctl; +a VMM-stopped plane 0 keeps its events pending until the VMM makes it +runnable, so a software privilege model can keep a higher plane running +undisturbed. In-kernel plane transitions never override a userspace +stop: a guest-initiated VTL switch targeting a stopped plane is +forwarded to userspace instead. The ioctl is accepted on any plane's vCPU fd. +KVM_PLANE_RUN_STATE_KERNEL_STOPPED is reported for a plane that KVM +itself suspended (such as the lower plane of an in-kernel VTL switch); +such a plane becomes runnable again on the matching in-kernel transition +without userspace involvement. Setting it stops the plane without +recording a userspace stop, so restoring previously read run states +round-trips a kernel suspension without converting it into userspace +stop policy. + The run state is distinct from KVM_GET/SET_MP_STATE, which describes the architectural state of a vCPU within its plane. diff --git a/arch/x86/hyperv/hv_apic.c b/arch/x86/hyperv/hv_apic.c index a8de503def37..533304c29603 100644 --- a/arch/x86/hyperv/hv_apic.c +++ b/arch/x86/hyperv/hv_apic.c @@ -127,7 +127,8 @@ static bool __send_ipi_mask_ex(const struct cpumask *mask, int vector, goto ipi_mask_ex_done; ipi_arg->vector = vector; - ipi_arg->reserved = 0; + ipi_arg->in_vtl.as_uint8 = 0; + memset(ipi_arg->reserved, 0, sizeof(ipi_arg->reserved)); ipi_arg->vp_set.valid_bank_mask = 0; /* diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index 537ea6cded7a..74810e0e9ed7 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -732,13 +732,32 @@ struct kvm_vcpu_hv { u32 vp_id; } nested; + /* + * HvRegisterCrInterceptControl value the higher VTL armed for the + * lower VTL (userspace VSM): descriptor-table write bits divert + * the emulated write into a KVM_EXIT_HYPERV_REG_INTERCEPT exit. + */ + u64 reg_intercept_control; + struct { + u32 reg; + u8 insn_len; + bool pending; + u64 base; + u32 limit; + u64 rip; + } reg_intercept; + /* * RAX/RCX of the lower VTL captured at VTL entry, restored on a * non-fast VTL return when this plane has no VP assist page to - * carry them in its VTL control area. + * carry them in its VTL control area. Only valid between an + * in-kernel VTL entry and the matching return: a plane entered by + * userspace, or restored from migration, has no captured values + * and its assist-less return is forwarded to userspace. */ u64 vtl_entry_rax; u64 vtl_entry_rcx; + bool vtl_entry_regs_valid; }; struct kvm_hypervisor_cpuid { diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c index 2f57f89cd8ea..ca31054ed065 100644 --- a/arch/x86/kvm/hyperv.c +++ b/arch/x86/kvm/hyperv.c @@ -2364,7 +2364,8 @@ static s64 kvm_hv_flush_tlb(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc) } static void kvm_hv_send_ipi_to_many(struct kvm *kvm, u32 vector, - u64 *sparse_banks, u64 valid_bank_mask) + u64 *sparse_banks, u64 valid_bank_mask, + int target_plane) { struct kvm_lapic_irq irq = { .delivery_mode = APIC_DM_FIXED, @@ -2374,13 +2375,25 @@ static void kvm_hv_send_ipi_to_many(struct kvm *kvm, u32 vector, unsigned long i; kvm_for_each_vcpu(i, vcpu, kvm) { + struct kvm_vcpu *target = vcpu; + if (sparse_banks && !hv_is_vp_in_sparse_set(kvm_hv_get_vpindex(vcpu), valid_bank_mask, sparse_banks)) continue; + /* + * TLFS: the IPI targets the VPs' in_vtl (or the sender's + * VTL): deliver into that plane's APIC. + */ + if (target_plane) { + target = vcpu->common->vcpus[target_plane]; + if (!target) + continue; + } + /* We fail only when APIC is disabled */ - kvm_apic_set_irq(vcpu, &irq, NULL); + kvm_apic_set_irq(target, &irq, NULL); } } @@ -2393,6 +2406,8 @@ static int kvm_hv_send_ipi(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc) struct hv_send_ipi send_ipi; u64 valid_bank_mask; u32 vector; + u8 in_vtl = 0; + int target_plane; bool all_cpus; int r; @@ -2410,13 +2425,25 @@ static int kvm_hv_send_ipi(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc) return HV_STATUS_INVALID_HYPERCALL_INPUT; sparse_banks[0] = send_ipi.cpu_mask; vector = send_ipi.vector; + in_vtl = send_ipi.in_vtl.as_uint8; } else { /* 'reserved' part of hv_send_ipi should be 0 */ - if (unlikely(hc->ingpa >> 32 != 0)) + if (unlikely(hc->ingpa >> 40 != 0)) return HV_STATUS_INVALID_HYPERCALL_INPUT; sparse_banks[0] = hc->outgpa; vector = (u32)hc->ingpa; + in_vtl = (hc->ingpa >> 32) & 0xff; } + + /* + * Only hv_input_vtl.target_vtl (bits 3:0) and + * use_target_vtl (bit 4) are defined; reserved_z (7:5) + * must be zero in every form of the hypercall, before + * anything is delivered. + */ + if (unlikely(in_vtl & 0xe0)) + return HV_STATUS_INVALID_HYPERCALL_INPUT; + all_cpus = false; valid_bank_mask = BIT_ULL(0); @@ -2426,12 +2453,18 @@ static int kvm_hv_send_ipi(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc) if (unlikely(kvm_read_guest(kvm, hc->ingpa, &send_ipi_ex, sizeof(send_ipi_ex)))) return HV_STATUS_INVALID_HYPERCALL_INPUT; + in_vtl = send_ipi_ex.in_vtl.as_uint8; } else { send_ipi_ex.vector = (u32)hc->ingpa; send_ipi_ex.vp_set.format = hc->outgpa; send_ipi_ex.vp_set.valid_bank_mask = sse128_lo(hc->xmm[0]); + in_vtl = (hc->ingpa >> 32) & 0xff; } + /* As above: hv_input_vtl bits 7:5 are reserved. */ + if (unlikely(in_vtl & 0xe0)) + return HV_STATUS_INVALID_HYPERCALL_INPUT; + trace_kvm_hv_send_ipi_ex(send_ipi_ex.vector, send_ipi_ex.vp_set.format, send_ipi_ex.vp_set.valid_bank_mask); @@ -2463,10 +2496,19 @@ static int kvm_hv_send_ipi(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc) if ((vector < HV_IPI_LOW_VECTOR) || (vector > HV_IPI_HIGH_VECTOR)) return HV_STATUS_INVALID_HYPERCALL_INPUT; + /* TLFS: in_vtl bit 4 selects an explicit target VTL. */ + if (in_vtl & 0x10) + target_plane = in_vtl & 0xf; + else + target_plane = vcpu->plane_level; + if (target_plane >= KVM_MAX_PLANES) + return HV_STATUS_INVALID_HYPERCALL_INPUT; + if (all_cpus) - kvm_hv_send_ipi_to_many(kvm, vector, NULL, 0); + kvm_hv_send_ipi_to_many(kvm, vector, NULL, 0, target_plane); else - kvm_hv_send_ipi_to_many(kvm, vector, sparse_banks, valid_bank_mask); + kvm_hv_send_ipi_to_many(kvm, vector, sparse_banks, + valid_bank_mask, target_plane); ret_success: return HV_STATUS_SUCCESS; @@ -2772,6 +2814,116 @@ static u64 hv_vtl_assist_page_gpa(struct kvm_vcpu *vcpu) * Returns -ENOENT when the transition cannot be handled here (no twin * plane); the caller forwards the hypercall to userspace unchanged. */ +/* + * Enter a higher VTL because it has a pending interrupt (TLFS: higher + * VTL interrupts preempt lower VTL execution). The lower VTL is + * suspended mid-instruction, not in a hypercall, so no completion is + * pended for it: it resumes at the preempted RIP when the higher VTL + * returns, and the entered VTL's control area carries the INTERRUPT + * entry reason with the preempted RAX/RCX. + */ +#define HV_VTL_ENTRY_REASON_INTERRUPT 2 + +/* + * States in which the in-kernel paths must not enter a plane: + * + * - never entered the guest: it still holds reset state (userspace owns + * HvCallEnableVpVtl and runs a plane's first entry itself); + * - stopped by userspace with KVM_SET_PLANE_RUN_STATE: the VMM owns + * restarting it; + * - parked on a non-hypercall completion (MMIO, PIO): the completion + * owns the shared run union, which the resume protocol would clobber. + * + * The forced interrupt entry refuses such a target (the event stays + * pending), and kvm_higher_plane_with_event() must not report its + * pending event as a wakeup either, or a halted lower VTL spins through + * hlt/wake with nothing able to consume the event. + */ +bool kvm_hv_vtl_entry_blocked(struct kvm_vcpu *target) +{ + if (target->arch.last_vmentry_cpu == -1) + return true; + + if (target->plane_user_stopped) + return true; + + if (target->arch.complete_userspace_io && + target->arch.complete_userspace_io != kvm_hv_hypercall_complete_userspace) + return true; + + return false; +} + +bool kvm_hv_vtl_interrupt_entry(struct kvm_vcpu *vcpu, + struct kvm_vcpu *target) +{ + u64 rax = kvm_rax_read(vcpu), rcx = kvm_rcx_read(vcpu); + struct hv_vtl_control ctl; + u64 assist; + int i; + + static const int shared_gprs[] = { + VCPU_REGS_RAX, VCPU_REGS_RBX, VCPU_REGS_RCX, VCPU_REGS_RDX, + VCPU_REGS_RSI, VCPU_REGS_RDI, VCPU_REGS_RBP, + VCPU_REGS_R8, VCPU_REGS_R9, VCPU_REGS_R10, VCPU_REGS_R11, + VCPU_REGS_R12, VCPU_REGS_R13, VCPU_REGS_R14, VCPU_REGS_R15, + }; + + /* The event stays pending until the plane can be entered. */ + if (kvm_hv_vtl_entry_blocked(target)) + return false; + + /* + * Write the entered plane's control area through the entered + * plane's vCPU: the write must pass that plane's own memory + * attributes and memslot view, not the suspending plane's (which + * typically has no access to the higher VTL's assist page). On + * failure leave both planes untouched; the event stays pending in + * the target and the entry is retried. + */ + assist = hv_vtl_assist_page_gpa(target); + if (assist) { + ctl = (struct hv_vtl_control) { + .vtl_entry_reason = HV_VTL_ENTRY_REASON_INTERRUPT, + .vtl_ret_x64rax = rax, + .vtl_ret_x64rcx = rcx, + }; + if (kvm_vcpu_write_guest(target, assist + HV_VTL_CONTROL_OFFSET, + &ctl, sizeof(ctl))) + return false; + } + + for (i = 0; i < ARRAY_SIZE(shared_gprs); i++) + kvm_register_write_raw(target, shared_gprs[i], + kvm_register_read_raw(vcpu, + shared_gprs[i])); + target->arch.cr2 = vcpu->arch.cr2; + vcpu->common->vtl_fpu_from = vcpu->plane_level; + + if (to_hv_vcpu(target)) { + to_hv_vcpu(target)->vtl_entry_rax = rax; + to_hv_vcpu(target)->vtl_entry_rcx = rcx; + to_hv_vcpu(target)->vtl_entry_regs_valid = true; + } + + /* + * The target resumes its suspended hypercall, if it has one. The + * resume value is the shared RAX as copied from the entering plane + * just above: GPRs are VTL-shared, so the retired hypercall's RAX + * must match what every other shared register already shows. The + * value is guest-derived; strip the VMM-only RAISE_UD control bit + * so the completion cannot be made to inject #UD by the guest. + */ + if (target->arch.complete_userspace_io) + vcpu->run->hyperv.u.hcall.result = kvm_rax_read(target) & + ~KVM_HV_HCALL_RESULT_RAISE_UD; + + vcpu->plane_kernel_stopped = true; + kvm_vcpu_set_plane_stopped(vcpu); + kvm_vcpu_set_plane_runnable(target); + return true; +} + static int hv_vtl_switch_limit = -1; module_param(hv_vtl_switch_limit, int, 0644); @@ -2797,6 +2949,35 @@ static int kvm_hv_vtl_switch(struct kvm_vcpu *vcpu, bool is_return) return -ENOENT; target = vcpu->common->vcpus[to]; + /* + * A VMM-stopped plane stays stopped until the VMM restarts it. + * Forward the switch so userspace can apply its own policy. + */ + if (target->plane_user_stopped) + return -ENOENT; + + /* + * The resume protocol below delivers the result through the + * target's armed hypercall completion. A target parked on any + * other completion (MMIO, PIO) owns the shared run union; writing + * hcall.result over it would feed the pending completion garbage + * and lose the resume value. Forward such a switch to userspace. + */ + if (target->arch.complete_userspace_io && + target->arch.complete_userspace_io != kvm_hv_hypercall_complete_userspace) + return -ENOENT; + + /* + * Userspace owns HvCallEnableVpVtl state; the kernel only knows a + * plane vCPU exists, and VMMs create those at setup time. Refuse + * to call into a plane that has never entered the guest: it still + * holds reset state, and the reference host fails such a call with + * HV_STATUS_INVALID_VP_STATE. Forwarding lets userspace apply its + * enablement tracking (and run the plane's first entry itself). + */ + if (!is_return && target->arch.last_vmentry_cpu == -1) + return -ENOENT; + /* * Debug budget on in-kernel switches, counted per VM and only for * switches that pass the eligibility checks above (a forwarded @@ -2816,9 +2997,39 @@ static int kvm_hv_vtl_switch(struct kvm_vcpu *vcpu, bool is_return) &ctl, sizeof(ctl))) return -ENOENT; } else { + /* + * No captured entry registers to restore: this plane + * was last entered by userspace (or the capture was + * lost across migration). Forward the return so + * userspace restores the lower VTL's RAX/RCX itself. + */ + if (!to_hv_vcpu(vcpu)->vtl_entry_regs_valid) + return -ENOENT; + ctl.vtl_ret_x64rax = to_hv_vcpu(vcpu)->vtl_entry_rax; ctl.vtl_ret_x64rcx = to_hv_vcpu(vcpu)->vtl_entry_rcx; } + } else if (!is_return) { + /* + * Write the entered VTL's control area through the entered + * plane's vCPU: the write must pass that plane's memory + * attributes and memslot view, and the higher VTL normally + * protects its assist page against the caller's plane. + * Fail before any state moves (mirroring the read above); + * userspace then owns the switch. + */ + assist = hv_vtl_assist_page_gpa(target); + if (assist) { + ctl = (struct hv_vtl_control) { + .vtl_entry_reason = HV_VTL_ENTRY_REASON_VTL_CALL, + .vtl_ret_x64rax = rax, + .vtl_ret_x64rcx = rcx, + }; + if (kvm_vcpu_write_guest(target, + assist + HV_VTL_CONTROL_OFFSET, + &ctl, sizeof(ctl))) + return -ENOENT; + } } /* VTL-shared state: GPRs except RSP (RIP/RFLAGS stay private). */ @@ -2830,26 +3041,17 @@ static int kvm_hv_vtl_switch(struct kvm_vcpu *vcpu, bool is_return) vcpu->common->vtl_fpu_from = cur; if (!is_return) { - assist = hv_vtl_assist_page_gpa(target); - if (assist) { - ctl = (struct hv_vtl_control) { - .vtl_entry_reason = HV_VTL_ENTRY_REASON_VTL_CALL, - .vtl_ret_x64rax = rax, - .vtl_ret_x64rcx = rcx, - }; - kvm_vcpu_write_guest(vcpu, - assist + HV_VTL_CONTROL_OFFSET, - &ctl, sizeof(ctl)); - } if (to_hv_vcpu(target)) { to_hv_vcpu(target)->vtl_entry_rax = rax; to_hv_vcpu(target)->vtl_entry_rcx = rcx; + to_hv_vcpu(target)->vtl_entry_regs_valid = true; } resume = rax; } else { if (!fast) kvm_rcx_write(target, ctl.vtl_ret_x64rcx); resume = fast ? rax : ctl.vtl_ret_x64rax; + to_hv_vcpu(vcpu)->vtl_entry_regs_valid = false; } /* @@ -2859,9 +3061,27 @@ static int kvm_hv_vtl_switch(struct kvm_vcpu *vcpu, bool is_return) * exactly as when userspace drives the switch; until then its * suspended RIP stays at the vmcall, which the higher VTL can * observe (and rewrite) through HvCallGetVpRegisters. + * + * The resume value is guest-derived in all three cases (the + * caller's RAX on a call or a fast return, the control area on a + * non-fast return): strip the VMM-only RAISE_UD control bit so + * the completion cannot be made to inject #UD into the suspended + * plane. */ + resume &= ~KVM_HV_HCALL_RESULT_RAISE_UD; if (target->arch.complete_userspace_io) vcpu->run->hyperv.u.hcall.result = resume; + else + /* + * No completion is armed when the target's suspension + * already retired its vmcall (forwarded switch) or was a + * forced interrupt entry: RIP is already where the plane + * resumes, so deliver the resume RAX directly. Without + * this the GPR copy above leaves the switching plane's raw + * RAX in place and a non-fast return never restores the + * control-area value. + */ + kvm_rax_write(target, resume); /* * Suspend the current plane in its uncompleted hypercall; the @@ -2869,9 +3089,8 @@ static int kvm_hv_vtl_switch(struct kvm_vcpu *vcpu, bool is_return) */ vcpu->arch.complete_userspace_io = kvm_hv_hypercall_complete_userspace; - vcpu->plane_user_stopped = true; + vcpu->plane_kernel_stopped = true; kvm_vcpu_set_plane_stopped(vcpu); - target->plane_user_stopped = false; kvm_vcpu_set_plane_runnable(target); kvm_make_request(KVM_REQ_PLANE_RESCHED, vcpu); return 0; @@ -3050,7 +3269,7 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu) ret = HV_STATUS_INVALID_HYPERCALL_INPUT; break; } - if (to_kvm_hv(vcpu->kvm)->hv_inkernel_vtl_switch && + if (READ_ONCE(to_kvm_hv(vcpu->kvm)->hv_inkernel_vtl_switch) && !hc.fast && !kvm_hv_vtl_switch(vcpu, hc.code == HVCALL_VTL_RETURN)) { /* diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h index 0593fe4333d3..0b81ba482201 100644 --- a/arch/x86/kvm/hyperv.h +++ b/arch/x86/kvm/hyperv.h @@ -102,6 +102,9 @@ static inline bool kvm_hv_hypercall_enabled(struct kvm_vcpu *vcpu) } int kvm_hv_hypercall(struct kvm_vcpu *vcpu); +bool kvm_hv_vtl_interrupt_entry(struct kvm_vcpu *vcpu, + struct kvm_vcpu *target); +bool kvm_hv_vtl_entry_blocked(struct kvm_vcpu *target); void kvm_hv_irq_routing_update(struct kvm *kvm); int kvm_hv_synic_set_irq(struct kvm_kernel_irq_routing_entry *e, struct kvm *kvm, diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index d10aa5f60cad..e1c5f1608b09 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -5640,7 +5640,14 @@ static int handle_desc(struct kvm_vcpu *vcpu) */ BUILD_BUG_ON(KVM_POSSIBLE_CR4_GUEST_BITS & X86_CR4_UMIP); - WARN_ON_ONCE(!kvm_is_cr4_bit_set(vcpu, X86_CR4_UMIP)); + /* + * Descriptor-table exits are also armed when a higher VTL registers + * DT intercepts (reg_intercept_control); only an exit with neither + * UMIP emulation nor an armed intercept indicates a stale + * SECONDARY_EXEC_DESC control. + */ + WARN_ON_ONCE(!kvm_is_cr4_bit_set(vcpu, X86_CR4_UMIP) && + !(to_hv_vcpu(vcpu) && to_hv_vcpu(vcpu)->reg_intercept_control)); return kvm_emulate_instruction(vcpu, 0); } @@ -8044,6 +8051,20 @@ void vmx_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu) /* Refresh #PF interception to account for MAXPHYADDR changes. */ vmx_update_exception_bitmap(vcpu); + + /* + * Trap descriptor-table instructions when a higher VTL armed + * register intercepts on them, and stop trapping when the + * intercepts are disarmed. Preserve the control when UMIP + * emulation needs it (CR4.UMIP owns it then). + */ + if (vmcs_config.cpu_based_2nd_exec_ctrl & SECONDARY_EXEC_DESC) { + if (to_hv_vcpu(vcpu) && to_hv_vcpu(vcpu)->reg_intercept_control) + secondary_exec_controls_setbit(vmx, SECONDARY_EXEC_DESC); + else if (!(vmx_umip_emulated() && + kvm_is_cr4_bit_set(vcpu, X86_CR4_UMIP))) + secondary_exec_controls_clearbit(vmx, SECONDARY_EXEC_DESC); + } } static __init u64 vmx_get_perf_capabilities(void) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index aa8e14a45568..374330f61eb2 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -4885,6 +4885,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) case KVM_CAP_SYS_HYPERV_CPUID: case KVM_CAP_HYPERV_FORWARD_HCALL: case KVM_CAP_HYPERV_VTL_SWITCH: + case KVM_CAP_HYPERV_REG_INTERCEPT: #endif case KVM_CAP_HCALL_FAULT_EXIT: case KVM_CAP_PCI_SEGMENT: @@ -6144,6 +6145,19 @@ static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu, } return r; } + case KVM_CAP_HYPERV_REG_INTERCEPT: { + struct kvm_vcpu_hv *hv_vcpu; + int r; + + r = kvm_hv_vcpu_init(vcpu); + if (r) + return r; + hv_vcpu = to_hv_vcpu(vcpu); + hv_vcpu->reg_intercept_control = cap->args[0]; + /* Refresh the descriptor-table exiting controls. */ + kvm_x86_call(vcpu_after_set_cpuid)(vcpu); + return 0; + } case KVM_CAP_HYPERV_DIRECT_TLBFLUSH: if (!kvm_x86_ops.enable_l2_tlb_flush) return -ENOTTY; @@ -6931,7 +6945,7 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, r = -EINVAL; if (cap->args[0] & ~1ULL) break; - to_kvm_hv(kvm)->hv_inkernel_vtl_switch = !!cap->args[0]; + WRITE_ONCE(to_kvm_hv(kvm)->hv_inkernel_vtl_switch, !!cap->args[0]); r = 0; break; #endif @@ -8814,14 +8828,55 @@ static void emulator_get_idt(struct x86_emulate_ctxt *ctxt, struct desc_ptr *dt) kvm_x86_call(get_idt)(emul_to_vcpu(ctxt), dt); } +/* + * Divert a descriptor-table write into a register-intercept exit when the + * higher VTL armed one: the write is not applied, and userspace delivers + * the TLFS register-intercept message to the higher VTL. + */ +static bool hv_divert_dt_write(struct x86_emulate_ctxt *ctxt, u32 hv_reg, + const struct desc_ptr *dt, u64 ctl_bit) +{ +#ifdef CONFIG_KVM_HYPERV + struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt); + struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu); + + if (!hv_vcpu || vcpu->plane_level || + !(hv_vcpu->reg_intercept_control & ctl_bit)) + return false; + + hv_vcpu->reg_intercept.reg = hv_reg; + hv_vcpu->reg_intercept.base = dt->address; + hv_vcpu->reg_intercept.limit = dt->size; + hv_vcpu->reg_intercept.rip = ctxt->eip; + hv_vcpu->reg_intercept.insn_len = ctxt->_eip - ctxt->eip; + hv_vcpu->reg_intercept.pending = true; + return true; +#else + return false; +#endif +} + +#define HV_CR_INTERCEPT_GDTR_WRITE BIT_ULL(15) +#define HV_CR_INTERCEPT_IDTR_WRITE BIT_ULL(16) +#define HV_X64_REGISTER_IDTR_ID 0x00070000 +#define HV_X64_REGISTER_GDTR_ID 0x00070001 + static void emulator_set_gdt(struct x86_emulate_ctxt *ctxt, struct desc_ptr *dt) { + if (hv_divert_dt_write(ctxt, HV_X64_REGISTER_GDTR_ID, dt, + HV_CR_INTERCEPT_GDTR_WRITE)) + return; kvm_x86_call(set_gdt)(emul_to_vcpu(ctxt), dt); } static void emulator_set_idt(struct x86_emulate_ctxt *ctxt, struct desc_ptr *dt) { - kvm_x86_call(set_idt)(emul_to_vcpu(ctxt), dt); + struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt); + + if (hv_divert_dt_write(ctxt, HV_X64_REGISTER_IDTR_ID, dt, + HV_CR_INTERCEPT_IDTR_WRITE)) + return; + kvm_x86_call(set_idt)(vcpu, dt); } static unsigned long emulator_get_cached_segment_base( @@ -9607,6 +9662,44 @@ int x86_decode_emulated_instruction(struct kvm_vcpu *vcpu, int emulation_type, } EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_decode_emulated_instruction); +#ifdef CONFIG_KVM_HYPERV +/* + * Complete a descriptor-table write that hv_divert_dt_write() diverted + * during the emulation that just finished: rewind RIP and build the + * KVM_EXIT_HYPERV_REG_INTERCEPT exit. Consuming the record before + * x86_emulate_instruction() returns means it never outlives the emulation + * call: no stale rewind can fire on a later KVM_RUN, and disarming the cap + * or migrating the vCPU can never see a half-delivered intercept. + */ +static int complete_hv_reg_intercept(struct kvm_vcpu *vcpu) +{ + struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu); + + hv_vcpu->reg_intercept.pending = false; + + /* + * The intercepted instruction must not retire: the emulator advanced + * RIP when it diverted the write, but the higher VTL owns + * completion. It either applies the write and advances RIP itself, + * disarms the intercept so re-execution succeeds, or queues an + * exception that must fault AT the instruction (the + * kernel-patch-protection LIDT probe matches the faulting RIP + * against the probe site). + */ + kvm_rip_write(vcpu, hv_vcpu->reg_intercept.rip); + vcpu->run->exit_reason = KVM_EXIT_HYPERV; + vcpu->run->hyperv.type = KVM_EXIT_HYPERV_REG_INTERCEPT; + vcpu->run->hyperv.plane = vcpu->plane_level; + vcpu->run->hyperv.u.reg_intercept.reg = hv_vcpu->reg_intercept.reg; + vcpu->run->hyperv.u.reg_intercept.insn_len = + hv_vcpu->reg_intercept.insn_len; + vcpu->run->hyperv.u.reg_intercept.base = hv_vcpu->reg_intercept.base; + vcpu->run->hyperv.u.reg_intercept.limit = hv_vcpu->reg_intercept.limit; + vcpu->run->hyperv.u.reg_intercept.rip = hv_vcpu->reg_intercept.rip; + return 0; +} +#endif + int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, int emulation_type, void *insn, int insn_len) { @@ -9826,6 +9919,15 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, } else vcpu->arch.emulate_regs_need_sync_to_vcpu = true; +#ifdef CONFIG_KVM_HYPERV + { + struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu); + + if (hv_vcpu && hv_vcpu->reg_intercept.pending) + r = complete_hv_reg_intercept(vcpu); + } +#endif + return r; } @@ -11083,6 +11185,77 @@ static int kvm_check_and_inject_events(struct kvm_vcpu *vcpu, return r; } +/* + * A pending interrupt for a higher plane preempts this one: the higher + * VTL's interrupts take priority over lower-VTL execution (TLFS 15.2.4), + * and the secure kernel relies on it, e.g. to freeze the other processors' + * VTL1 while starting a new one. + */ +static struct kvm_vcpu *kvm_higher_plane_with_event(struct kvm_vcpu *vcpu) +{ + struct kvm_vcpu *higher; + int i; + + for (i = vcpu->plane_level + 1; i < KVM_MAX_PLANES; i++) { + higher = vcpu->common->vcpus[i]; + if (!higher) + continue; +#ifdef CONFIG_KVM_HYPERV + /* + * With in-kernel switching, an event on a plane the forced + * entry would refuse (never run, user-stopped, parked on a + * foreign completion) has no consumer until userspace + * starts the plane; reporting it would wake a halted lower + * VTL into an endless hlt/wake loop. With userspace + * switching the event is consumable via the interrupt exit, + * so it does count. + */ + if (READ_ONCE(to_kvm_hv(vcpu->kvm)->hv_inkernel_vtl_switch) && + kvm_hv_vtl_entry_blocked(higher)) + continue; +#endif + /* + * Test raw pending-ness, not injectability: + * kvm_cpu_has_injectable_intr() reports false on an + * APICv-active vCPU, and a suspended plane may carry a + * stale apicv_active until it next runs. A pending + * synthetic-timer request counts as well: it can only be + * processed (and its interrupt delivered) once the higher + * plane runs, so it would otherwise never fire while the + * plane is suspended. + */ + if (kvm_cpu_has_extint(higher) || + kvm_apic_has_interrupt(higher) != -1 || + kvm_test_request(KVM_REQ_HV_STIMER, higher)) + return higher; + } + return NULL; +} + +#ifdef CONFIG_KVM_HYPERV +/* + * With userspace owning VTL transitions, report a pending higher-plane + * event as an exit so userspace can enter the higher VTL. Returns true + * when the exit was prepared. + */ +static bool kvm_hv_vtl_interrupt_exit(struct kvm_vcpu *vcpu) +{ + struct kvm_vcpu *higher; + + if (READ_ONCE(to_kvm_hv(vcpu->kvm)->hv_inkernel_vtl_switch)) + return false; + + higher = kvm_higher_plane_with_event(vcpu); + if (!higher) + return false; + + vcpu->run->exit_reason = KVM_EXIT_HYPERV; + vcpu->run->hyperv.type = KVM_EXIT_HYPERV_VTL_INTERRUPT; + vcpu->run->hyperv.plane = higher->plane_level; + return true; +} +#endif + static inline bool kvm_check_plane0_events(struct kvm_vcpu *vcpu) { struct kvm_vcpu *vcpu_plane0; @@ -11548,13 +11721,30 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) } if (kvm_check_plane0_events(vcpu) && - !vcpu->common->vcpus[0]->plane_user_stopped) { + !vcpu->common->vcpus[0]->plane_user_stopped && + !vcpu->common->vcpus[0]->plane_kernel_stopped) { kvm_vcpu_set_plane_runnable(vcpu->common->vcpus[0]); kvm_make_request(KVM_REQ_EVENT, vcpu); kvm_make_request(KVM_REQ_PLANE_RESCHED, vcpu); } +#ifdef CONFIG_KVM_HYPERV + if (kvm_hv_vtl_interrupt_exit(vcpu)) { + r = 0; + goto out; + } + + if (READ_ONCE(to_kvm_hv(vcpu->kvm)->hv_inkernel_vtl_switch)) { + struct kvm_vcpu *higher = kvm_higher_plane_with_event(vcpu); + + if (higher && kvm_hv_vtl_interrupt_entry(vcpu, higher)) { + kvm_make_request(KVM_REQ_EVENT, higher); + kvm_make_request(KVM_REQ_PLANE_RESCHED, vcpu); + } + } +#endif + if (kvm_check_request(KVM_REQ_PLANE_RESCHED, vcpu)) { vcpu->common->plane_switch = true; r = 0; @@ -11904,7 +12094,8 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_vcpu_has_events); int kvm_arch_vcpu_runnable(struct kvm_vcpu *vcpu) { return kvm_vcpu_running(vcpu) || vcpu->arch.pv.pv_unhalted || - kvm_vcpu_has_events(vcpu); + kvm_vcpu_has_events(vcpu) || + kvm_higher_plane_with_event(vcpu); } /* Called within kvm->srcu read side. */ @@ -11999,6 +12190,17 @@ static int vcpu_run(struct kvm_vcpu *vcpu) if (r <= 0) break; +#ifdef CONFIG_KVM_HYPERV + /* + * A halted lower VTL never reaches vcpu_enter_guest, but a + * pending higher-VTL event must still preempt it. + */ + if (!kvm_vcpu_running(vcpu) && kvm_hv_vtl_interrupt_exit(vcpu)) { + r = 0; + break; + } +#endif + kvm_clear_request(KVM_REQ_UNBLOCK, vcpu); if (kvm_xen_has_pending_events(vcpu)) kvm_xen_inject_pending_events(vcpu); diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h index e1b6ee863777..956e9fb7ec33 100644 --- a/include/hyperv/hvgdk_mini.h +++ b/include/hyperv/hvgdk_mini.h @@ -1346,7 +1346,8 @@ struct hv_input_set_vp_registers { /* HvCallSendSyntheticClusterIpi hypercall */ struct hv_send_ipi { /* HV_INPUT_SEND_SYNTHETIC_CLUSTER_IPI */ u32 vector; - u32 reserved; + union hv_input_vtl in_vtl; + u8 reserved[3]; u64 cpu_mask; } __packed; diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h index b4cb2fa26e9b..a2180b35f646 100644 --- a/include/hyperv/hvhdk_mini.h +++ b/include/hyperv/hvhdk_mini.h @@ -464,7 +464,8 @@ struct hv_input_unmap_device_interrupt { struct hv_send_ipi_ex { /* HV_INPUT_SEND_SYNTHETIC_CLUSTER_IPI_EX */ u32 vector; - u32 reserved; + union hv_input_vtl in_vtl; + u8 reserved[3]; struct hv_vpset vp_set; } __packed; diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 79b3ec2b95b8..a76615f2c3dc 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -427,8 +427,20 @@ struct kvm_vcpu { * Set when userspace stopped this plane with * KVM_SET_PLANE_RUN_STATE: pending plane-0 events then no longer * make plane 0 runnable on their own, the VMM owns the switch. + * Only the ioctl sets it; in-kernel plane transitions never + * override it, and kvm_vcpu_set_plane_runnable() clears it so a + * runnable plane cannot carry a stale latch. */ bool plane_user_stopped; + /* + * Set when an in-kernel VTL switch suspends this plane: plane-0 + * events are gated exactly as for a userspace stop, but + * KVM_GET_PLANE_RUN_STATE reports the state as + * KVM_PLANE_RUN_STATE_KERNEL_STOPPED so that a save/restore + * round trip does not convert a transient kernel suspension + * into userspace stop policy. + */ + bool plane_kernel_stopped; /* S390 only */ bool valid_wakeup; diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index b50f92135acd..9e453f215e68 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -99,6 +99,8 @@ struct kvm_hyperv_exit { #define KVM_EXIT_HYPERV_SYNIC 1 #define KVM_EXIT_HYPERV_HCALL 2 #define KVM_EXIT_HYPERV_SYNDBG 3 +#define KVM_EXIT_HYPERV_VTL_INTERRUPT 4 +#define KVM_EXIT_HYPERV_REG_INTERCEPT 5 __u32 type; __u32 plane; union { @@ -114,6 +116,15 @@ struct kvm_hyperv_exit { __u64 result; __u64 params[2]; } hcall; + struct { + __u32 reg; + __u8 insn_len; + __u8 pad[3]; + __u64 base; + __u32 limit; + __u32 pad2; + __u64 rip; + } reg_intercept; struct { __u32 msr; __u32 pad2; @@ -1026,6 +1037,7 @@ struct kvm_enable_cap { #define KVM_HV_HCALL_RESULT_RAISE_UD (1ULL << 63) #define KVM_CAP_HCALL_FAULT_EXIT 252 #define KVM_CAP_HYPERV_VTL_SWITCH 253 +#define KVM_CAP_HYPERV_REG_INTERCEPT 254 struct kvm_irq_routing_irqchip { __u32 irqchip; @@ -1384,6 +1396,7 @@ struct kvm_s390_keyop { #define KVM_SET_PLANE_RUN_STATE _IOW(KVMIO, 0xe6, __u32) #define KVM_PLANE_RUN_STATE_STOPPED 0 #define KVM_PLANE_RUN_STATE_RUNNABLE 1 +#define KVM_PLANE_RUN_STATE_KERNEL_STOPPED 2 /* * ioctls for vcpu fds diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index ef6e59a15846..4197496f7176 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -5023,9 +5023,14 @@ static long kvm_vcpu_ioctl(struct file *filp, break; } case KVM_GET_PLANE_RUN_STATE: { - u32 state = vcpu->plane_state == RUNNABLE ? - KVM_PLANE_RUN_STATE_RUNNABLE : - KVM_PLANE_RUN_STATE_STOPPED; + u32 state; + + if (vcpu->plane_state == RUNNABLE) + state = KVM_PLANE_RUN_STATE_RUNNABLE; + else if (vcpu->plane_user_stopped) + state = KVM_PLANE_RUN_STATE_STOPPED; + else + state = KVM_PLANE_RUN_STATE_KERNEL_STOPPED; r = -EINVAL; if (kvm_arch_max_planes(vcpu->kvm) <= 1) @@ -5052,13 +5057,15 @@ static long kvm_vcpu_ioctl(struct file *filp, if (copy_from_user(&state, argp, sizeof(state))) goto out; r = -EINVAL; - if (state > KVM_PLANE_RUN_STATE_RUNNABLE) + if (state > KVM_PLANE_RUN_STATE_KERNEL_STOPPED) goto out; if (state == KVM_PLANE_RUN_STATE_RUNNABLE) { - vcpu->plane_user_stopped = false; kvm_vcpu_set_plane_runnable(vcpu); } else { - vcpu->plane_user_stopped = true; + vcpu->plane_user_stopped = + state == KVM_PLANE_RUN_STATE_STOPPED; + vcpu->plane_kernel_stopped = + state == KVM_PLANE_RUN_STATE_KERNEL_STOPPED; kvm_vcpu_set_plane_stopped(vcpu); } r = 0; @@ -5319,6 +5326,13 @@ static struct file_operations kvm_plane_fops = { void kvm_vcpu_set_plane_runnable(struct kvm_vcpu *vcpu) { + /* + * A runnable plane must not carry a stale stop latch: the latches + * gate the plane-0 event wakeup, and KVM_GET_PLANE_RUN_STATE could + * not report the inconsistency. + */ + vcpu->plane_user_stopped = false; + vcpu->plane_kernel_stopped = false; vcpu->plane_state = RUNNABLE; vcpu->common->plane_switch = true; kvm_make_request(KVM_REQ_PLANE_RESCHED, vcpu); @@ -5349,7 +5363,16 @@ struct kvm_vcpu *kvm_vcpu_select_plane(struct kvm_vcpu *vcpu) } if (ret == NULL) { + /* + * Never force a userspace-stopped plane 0 to run: it may be + * suspended mid-hypercall, and the VMM owns restarting it. + * KVM_RUN fails instead, surfacing the VMM's ordering bug. + */ ret = common->vcpus[0]; + if (ret->plane_user_stopped) + return NULL; + + ret->plane_kernel_stopped = false; ret->plane_state = RUNNABLE; } -- 2.50.1

