Skip to content

Commit bb723be

Browse files
Sean Christophersonbonzini
authored andcommitted
KVM: TDX: Handle TDX PV MMIO hypercall
Handle TDX PV MMIO hypercall when TDX guest calls TDVMCALL with the leaf #VE.RequestMMIO (same value as EXIT_REASON_EPT_VIOLATION) according to TDX Guest Host Communication Interface (GHCI) spec. For TDX guests, VMM is not allowed to access vCPU registers and the private memory, and the code instructions must be fetched from the private memory. So MMIO emulation implemented for non-TDX VMs is not possible for TDX guests. In TDX the MMIO regions are instead configured by VMM to trigger a #VE exception in the guest. The #VE handling is supposed to emulate the MMIO instruction inside the guest and convert it into a TDVMCALL with the leaf #VE.RequestMMIO, which equals to EXIT_REASON_EPT_VIOLATION. The requested MMIO address must be in shared GPA space. The shared bit is stripped after check because the existing code for MMIO emulation is not aware of the shared bit. The MMIO GPA shouldn't have a valid memslot, also the attribute of the GPA should be shared. KVM could do the checks before exiting to userspace, however, even if KVM does the check, there still will be race conditions between the check in KVM and the emulation of MMIO access in userspace due to a memslot hotplug, or a memory attribute conversion. If userspace doesn't check the attribute of the GPA and the attribute happens to be private, it will not pose a security risk or cause an MCE, but it can lead to another issue. E.g., in QEMU, treating a GPA with private attribute as shared when it falls within RAM's range can result in extra memory consumption during the emulation to the access to the HVA of the GPA. There are two options: 1) Do the check both in KVM and userspace. 2) Do the check only in QEMU. This patch chooses option 2, i.e. KVM omits the memslot and attribute checks, and expects userspace to do the checks. Similar to normal MMIO emulation, try to handle the MMIO in kernel first, if kernel can't support it, forward the request to userspace. Export needed symbols used for MMIO handling. Fragments handling is not needed for TDX PV MMIO because GPA is provided, if a MMIO access crosses page boundary, it should be continuous in GPA. Also, the size is limited to 1, 2, 4, 8 bytes. No further split needed. Allow cross page access because no extra handling needed after checking both start and end GPA are shared GPAs. Suggested-by: Sean Christopherson <seanjc@google.com> Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com> Signed-off-by: Isaku Yamahata <isaku.yamahata@intel.com> Co-developed-by: Binbin Wu <binbin.wu@linux.intel.com> Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Message-ID: <20250222014225.897298-10-binbin.wu@linux.intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 33608aa commit bb723be

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

arch/x86/kvm/vmx/tdx.c

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,8 @@ static __always_inline u32 tdcall_to_vmx_exit_reason(struct kvm_vcpu *vcpu)
810810
switch (tdvmcall_leaf(vcpu)) {
811811
case EXIT_REASON_IO_INSTRUCTION:
812812
return tdvmcall_leaf(vcpu);
813+
case EXIT_REASON_EPT_VIOLATION:
814+
return EXIT_REASON_EPT_MISCONFIG;
813815
default:
814816
break;
815817
}
@@ -1190,6 +1192,107 @@ static int tdx_emulate_io(struct kvm_vcpu *vcpu)
11901192
return ret;
11911193
}
11921194

1195+
static int tdx_complete_mmio_read(struct kvm_vcpu *vcpu)
1196+
{
1197+
unsigned long val = 0;
1198+
gpa_t gpa;
1199+
int size;
1200+
1201+
gpa = vcpu->mmio_fragments[0].gpa;
1202+
size = vcpu->mmio_fragments[0].len;
1203+
1204+
memcpy(&val, vcpu->run->mmio.data, size);
1205+
tdvmcall_set_return_val(vcpu, val);
1206+
trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
1207+
return 1;
1208+
}
1209+
1210+
static inline int tdx_mmio_write(struct kvm_vcpu *vcpu, gpa_t gpa, int size,
1211+
unsigned long val)
1212+
{
1213+
if (!kvm_io_bus_write(vcpu, KVM_FAST_MMIO_BUS, gpa, 0, NULL)) {
1214+
trace_kvm_fast_mmio(gpa);
1215+
return 0;
1216+
}
1217+
1218+
trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, size, gpa, &val);
1219+
if (kvm_io_bus_write(vcpu, KVM_MMIO_BUS, gpa, size, &val))
1220+
return -EOPNOTSUPP;
1221+
1222+
return 0;
1223+
}
1224+
1225+
static inline int tdx_mmio_read(struct kvm_vcpu *vcpu, gpa_t gpa, int size)
1226+
{
1227+
unsigned long val;
1228+
1229+
if (kvm_io_bus_read(vcpu, KVM_MMIO_BUS, gpa, size, &val))
1230+
return -EOPNOTSUPP;
1231+
1232+
tdvmcall_set_return_val(vcpu, val);
1233+
trace_kvm_mmio(KVM_TRACE_MMIO_READ, size, gpa, &val);
1234+
return 0;
1235+
}
1236+
1237+
static int tdx_emulate_mmio(struct kvm_vcpu *vcpu)
1238+
{
1239+
struct vcpu_tdx *tdx = to_tdx(vcpu);
1240+
int size, write, r;
1241+
unsigned long val;
1242+
gpa_t gpa;
1243+
1244+
size = tdx->vp_enter_args.r12;
1245+
write = tdx->vp_enter_args.r13;
1246+
gpa = tdx->vp_enter_args.r14;
1247+
val = write ? tdx->vp_enter_args.r15 : 0;
1248+
1249+
if (size != 1 && size != 2 && size != 4 && size != 8)
1250+
goto error;
1251+
if (write != 0 && write != 1)
1252+
goto error;
1253+
1254+
/*
1255+
* TDG.VP.VMCALL<MMIO> allows only shared GPA, it makes no sense to
1256+
* do MMIO emulation for private GPA.
1257+
*/
1258+
if (vt_is_tdx_private_gpa(vcpu->kvm, gpa) ||
1259+
vt_is_tdx_private_gpa(vcpu->kvm, gpa + size - 1))
1260+
goto error;
1261+
1262+
gpa = gpa & ~gfn_to_gpa(kvm_gfn_direct_bits(vcpu->kvm));
1263+
1264+
if (write)
1265+
r = tdx_mmio_write(vcpu, gpa, size, val);
1266+
else
1267+
r = tdx_mmio_read(vcpu, gpa, size);
1268+
if (!r)
1269+
/* Kernel completed device emulation. */
1270+
return 1;
1271+
1272+
/* Request the device emulation to userspace device model. */
1273+
vcpu->mmio_is_write = write;
1274+
if (!write)
1275+
vcpu->arch.complete_userspace_io = tdx_complete_mmio_read;
1276+
1277+
vcpu->run->mmio.phys_addr = gpa;
1278+
vcpu->run->mmio.len = size;
1279+
vcpu->run->mmio.is_write = write;
1280+
vcpu->run->exit_reason = KVM_EXIT_MMIO;
1281+
1282+
if (write) {
1283+
memcpy(vcpu->run->mmio.data, &val, size);
1284+
} else {
1285+
vcpu->mmio_fragments[0].gpa = gpa;
1286+
vcpu->mmio_fragments[0].len = size;
1287+
trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, size, gpa, NULL);
1288+
}
1289+
return 0;
1290+
1291+
error:
1292+
tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
1293+
return 1;
1294+
}
1295+
11931296
static int handle_tdvmcall(struct kvm_vcpu *vcpu)
11941297
{
11951298
switch (tdvmcall_leaf(vcpu)) {
@@ -1569,6 +1672,8 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
15691672
return tdx_emulate_vmcall(vcpu);
15701673
case EXIT_REASON_IO_INSTRUCTION:
15711674
return tdx_emulate_io(vcpu);
1675+
case EXIT_REASON_EPT_MISCONFIG:
1676+
return tdx_emulate_mmio(vcpu);
15721677
default:
15731678
break;
15741679
}

arch/x86/kvm/x86.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14024,6 +14024,7 @@ EXPORT_SYMBOL_GPL(kvm_sev_es_string_io);
1402414024

1402514025
EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_entry);
1402614026
EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_exit);
14027+
EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_mmio);
1402714028
EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_fast_mmio);
1402814029
EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_inj_virq);
1402914030
EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_page_fault);

virt/kvm/kvm_main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5842,6 +5842,7 @@ int kvm_io_bus_read(struct kvm_vcpu *vcpu, enum kvm_bus bus_idx, gpa_t addr,
58425842
r = __kvm_io_bus_read(vcpu, bus, &range, val);
58435843
return r < 0 ? r : 0;
58445844
}
5845+
EXPORT_SYMBOL_GPL(kvm_io_bus_read);
58455846

58465847
int kvm_io_bus_register_dev(struct kvm *kvm, enum kvm_bus bus_idx, gpa_t addr,
58475848
int len, struct kvm_io_device *dev)

0 commit comments

Comments
 (0)