KVM SVM changes for 7.4

- Fix a benign-in-practice bug where KVM unintentionally sets a non-zero
    error code when delivering exceptions to SEV-ES+ guests.
 
  - Force a new ASID in both VMCBs on pCPU migration to fix a bug where KVM can
    run a VMCB with a stale ASID due to the tracking being per-CPU, not per-VMCB.
 
  - Return INVALID_INPUT to the guest instead of exiting to userspace with -EIO
    if KVM can't access "guest request" buffer, i.e. try to keep the guest alive
    if the guest or userspace goofs.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEKTobbabEP7vbhhN9OlYIJqCjN/0FAmrRnUsACgkQOlYIJqCj
 N/3K6Q/9HzoeLCOZHfJ+QUuAG5KjlL28n95vinH+UcOPiM/DlMH2TqsE86sCAmWM
 ldYuKeDftDYgCT6u6f9qejg4Cs2a+yC9LO13d/cUl47QYe33J4X2pvGyr4FAMv1Q
 2Zm0U9XUFX44U0AS9eNT5orp+npIJTnFPCgS1f8ODiMJE7OydtKOdKpyiTZpc3Bf
 g15EgjN+EOfVLUMtQpGaegGgGjeBNn1VcPW8QkkkBJE/BuAN3e9RIAKkucGHd2KH
 v1vjj722brc+zC5xmhbtG03NsBxnEshYmgyoepllXmRr6Lp/yb0av+VUPimDazA5
 q5bR0xVutyBWdO9pVTA1uLkVXZ3+KLKHyDedKATr4LBb4XJ7L3h/aAI0esPwRxiI
 AYDYiz4hxD6qoHOE7vugTRHIu73TTfHN+/JhVBX6MyhBULuttnI8Zl/VAHal1foI
 8oCCb5/DCXd854nDX2xwMvSIFrCjBGt+9LfKNnauG/4gjJrmWqFKdJ10vfZQrsPu
 pukL8EWZHsywVFHfxLqON1XmYuz52Yp2bzXIf7M9MLddTAZMeRXGn8nOqOkbA+h2
 +DLhDczU9iBodQ44tro87+qnmqnEHjRV9LJrLw5J6ahI5NV5oefifldrmpSAAG5A
 0Q1wus5J+8Wy7sBDgvuGmsCvhi8k96cfTxJhAdd1NwD2XfWFFOo=
 =MX9Z
 -----END PGP SIGNATURE-----

Merge tag 'kvm-x86-svm-7.4' of https://github.com/kvm-x86/linux into HEAD

KVM SVM changes for 7.4

 - Fix a benign-in-practice bug where KVM unintentionally sets a non-zero
   error code when delivering exceptions to SEV-ES+ guests.

 - Force a new ASID in both VMCBs on pCPU migration to fix a bug where KVM can
   run a VMCB with a stale ASID due to the tracking being per-CPU, not per-VMCB.

 - Return INVALID_INPUT to the guest instead of exiting to userspace with -EIO
   if KVM can't access "guest request" buffer, i.e. try to keep the guest alive
   if the guest or userspace goofs.
This commit is contained in:
Paolo Bonzini 2026-10-16 10:58:01 +02:00
commit e43190558b
4 changed files with 49 additions and 12 deletions

View File

@ -641,8 +641,8 @@ static inline void __unused_size_checks(void)
#define SVM_EVTINJ_TYPE_EXEPT (3 << SVM_EVTINJ_TYPE_SHIFT)
#define SVM_EVTINJ_TYPE_SOFT (4 << SVM_EVTINJ_TYPE_SHIFT)
#define SVM_EVTINJ_VALID (1 << 31)
#define SVM_EVTINJ_VALID_ERR (1 << 11)
#define SVM_EVTINJ_VALID BIT(31)
#define SVM_EVTINJ_VALID_ERR BIT(11)
#define SVM_EVTINJ_RESERVED_BITS ~(SVM_EVTINJ_VEC_MASK | SVM_EVTINJ_TYPE_MASK | \
SVM_EVTINJ_VALID_ERR | SVM_EVTINJ_VALID)

View File

@ -1502,6 +1502,7 @@ int svm_allocate_nested(struct vcpu_svm *svm)
if (!svm->nested.msrpm)
goto err_free_vmcb02;
svm->nested.vmcb02.cpu = -1;
svm->nested.initialized = true;
return 0;

View File

@ -4239,6 +4239,7 @@ static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_
struct kvm *kvm = svm->vcpu.kvm;
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
sev_ret_code fw_err = 0;
u8 tickle = 0;
int ret;
if (!is_sev_snp_guest(&svm->vcpu))
@ -4246,8 +4247,21 @@ static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_
guard(mutex)(&sev->guest_req_mutex);
if (kvm_read_guest(kvm, req_gpa, sev->guest_req_buf, PAGE_SIZE))
return -EIO;
if (kvm_read_guest(kvm, req_gpa, sev->guest_req_buf, PAGE_SIZE)) {
svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
return 1;
}
/*
* "Tickle" the response buffer to verify it's writable before sending
* the request to firmware, which will modify the VMPCK sequence number.
* Note, this is a best effort check and doesn't guard against TOCTOU
* issues. See below for more information.
*/
if (kvm_write_guest(kvm, resp_gpa, &tickle, sizeof(tickle))) {
svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
return 1;
}
data.gctx_paddr = __psp_pa(sev->snp_context);
data.req_paddr = __psp_pa(sev->guest_req_buf);
@ -4262,6 +4276,13 @@ static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_
if (ret && !fw_err)
return ret;
/*
* Exit to userspace if writing the response fails, e.g. if the mapping
* changed between the initial tickle and the actual write, to avoid
* creating an ambiguous failure ABI with the guest. Don't return an
* error to the guest because firmware already incremented the VMPCK
* sequence number, but there's no way to communicate that to the guest.
*/
if (kvm_write_guest(kvm, resp_gpa, sev->guest_resp_buf, PAGE_SIZE))
return -EIO;
@ -4313,8 +4334,10 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r
return -EINVAL;
if (kvm_read_guest(kvm, req_gpa + offsetof(struct snp_guest_msg_hdr, msg_type),
&msg_type, 1))
return -EIO;
&msg_type, 1)) {
svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
return 1;
}
/*
* As per GHCB spec, requests of type MSG_REPORT_REQ also allow for
@ -4353,8 +4376,10 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r
* As per GHCB spec (see "SNP Extended Guest Request"), the
* certificate table is terminated by 24-bytes of zeroes.
*/
if (data_npages && kvm_clear_guest(kvm, data_gpa, 24))
return -EIO;
if (data_npages && kvm_clear_guest(kvm, data_gpa, 24)) {
svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
return 1;
}
}
return snp_handle_guest_req(svm, req_gpa, resp_gpa);

View File

@ -3875,14 +3875,25 @@ static int pre_svm_run(struct kvm_vcpu *vcpu)
struct vcpu_svm *svm = to_svm(vcpu);
/*
* If the previous vmrun of the vmcb occurred on a different physical
* cpu, then mark the vmcb dirty and assign a new asid. Hardware's
* vmcb clean bits are per logical CPU, as are KVM's asid assignments.
* If the previous VMRUN of the VMCB occurred on a different physical
* cpu, then mark the VMCB dirty as hardware's clean bits are per pCPU.
*
* Reset the ASID generation in both VMCBs. This will lead to assigning
* a new ASID now, and then again when switching to the other VMCB.
* However, this is needed as the ASID is shared between the VMCBs, and
* otherwise it would be possible to use an ASID allocated on one pCPU
* on another, for example:
* - vCPU migrates from pCPU A to pCPU B, allocates a new ASID.
* - vCPU migrates back to pCPU A, and then switches the VMCB.
* - The new VMCB does not detect a pCPU change and runs on pCPU A with
* the new ASID allocated on pCPU B, which is potentially used by
* another vCPU/VM.
*/
if (unlikely(svm->current_vmcb->cpu != vcpu->cpu)) {
svm->current_vmcb->asid_generation = 0;
vmcb_mark_all_dirty(svm->vmcb);
svm->current_vmcb->cpu = vcpu->cpu;
svm->vmcb01.asid_generation = 0;
svm->nested.vmcb02.asid_generation = 0;
}
if (is_sev_guest(vcpu))