KVM: Out-Of-Bounds Read in nested_svm_get_tpd_pdptr
Summary
A Out-Of-Bounds (OOB) read affecting KVM since v3.10 was discovered in arch/x86/kvm/svm/nested.c. The memory read is from the user-space process managing the associated KVM based Virtual Machine (VM). The values passed from nested_svm_get_tdp_pdptr are not masked to prevent the access from crossing a page boundary and __kvm_read_guest_page lacks similar validation.
Severity
Moderate - The nested_svm_get_tdp_pdptr function allows a guest Virtual Machine Monitor (VMM) to read memory OOB from its guest physical address (GPA) space. The data read is used to construct Nested Page Tables (NPT) and could be used to indirectly leak memory in the host virtual address (HVA) space.
QEMU was used to develop the PoC and results in __copy_from_user returning an error and __kvm_read_guest_page returning -EFAULT. This is because QEMU adds a guard page directly before and after each region. Other products utilizing KVM could have different outcomes.
Proof-of-Concept
A kvm-unit-test was developed to create a 32-bit protected mode nested-VMM with PAE enabled. It uses existing code from the project and switches a nested-VMM from 64-bit long mode to the require system state and back after executing the VMRUN instruction. This setup triggers the nested_svm_get_tdp_pdptr execution path in KVM.
Modifying the vmcb->control.nested_cr3 value to a GPA at the...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| KVM Product | >= 3.2, < 5.15.170 | 5.15.170 |
Details and references
### Summary A Out-Of-Bounds (OOB) read affecting KVM since v3.10 was discovered in `arch/x86/kvm/svm/nested.c`. The memory read is from the user-space process managing the associated KVM based Virtual Machine (VM). The values passed from `nested_svm_get_tdp_pdptr` are not masked to prevent the access from crossing a page boundary and `__kvm_read_guest_page` lacks similar validation. ### Severity Moderate - The `nested_svm_get_tdp_pdptr` function allows a guest Virtual Machine Monitor (VMM) to read memory OOB from its guest physical address (GPA) space. The data read is used to construct Nested Page Tables (NPT) and could be used to indirectly leak memory in the host virtual address (HVA) space. QEMU was used to develop the PoC and results in `__copy_from_user` returning an error and `__kvm_read_guest_page` returning `-EFAULT`. This is because QEMU adds a guard page directly before and after each region. Other products utilizing KVM could have different outcomes. ## Proof-of-Concept A `kvm-unit-test` was developed to create a 32-bit protected mode nested-VMM with PAE enabled. It uses existing code from the project and switches a nested-VMM from 64-bit long mode to the require system state and back after executing the VMRUN instruction. This setup triggers the `nested_svm_get_tdp_pdptr` execution path in KVM. Modifying the `vmcb->control.nested_cr3` value to a GPA at the edge of any memory slot will cause the OOB read access to take place. There are multiple locations that meet this criteria but change based on attached virtual devices and amount of memory. Testing with QEMU resulted in a return value of `-EFAULT` because it places guard pages at the start and end of every memory region exposed to the VM. Other applications leveraging KVM could have different results. ### Build and Run with kvm-unit-test 1. Copy `svm_p32pae_ncr3.c` and `_svm_p32pae_ncr3.S` to the `x86` directory 2. Update `Makefile.x86_64` in the `x86` directory to include the contents from the snippet below 3. Run QEMU with `qemu-system-x86_64 -cpu host -m size=2048 -serial stdio -kernel svm_p32pae_ncr3.flat` ``` // Makefile.x86_64 ... # add this line right before the "include $(SRCDIR)/$(TEST_DIR)/Makefile.common" line tests += $(TEST_DIR)/svm_p32pae_ncr3.$(exe) ... # add this line to the end of the file $(TEST_DIR)/svm_p32pae_ncr3.$(bin): $(TEST_DIR)/svm_p32pae_ncr3.o $(TEST_DIR)/_svm_p32pae_ncr3.o ``` ``` // svm_p32pae_ncr3.c #include "x86/fwcfg.h" #include "x86/msr.h" #include "x86/processor.h" #include "alloc_page.h" #include "desc.h" #include "vmalloc.h" #include "x86/asm/page.h" #include "alloc_phys.h" #define SVM_EXIT_VMMCALL 0x081 #define SVM_EXIT_SHUTDOWN 0x07f #define SVM_EXIT_NPF 0x400 #define MSR_BITMAP_SIZE 8192 #define TLB_CONTROL_FLUSH_ALL_ASID 1 #define SVM_SELECTOR_S_SHIFT 4 #define SVM_SELECTOR_P_SHIFT 7 #define SVM_SELECTOR_DB_SHIFT 10 #define SVM_SELECTOR_G_SHIFT 11 #define SVM_SELECTOR_S_MASK (1 << SVM_SELECTOR_S_SHIFT) #define SVM_SELECTOR_P_MASK (1 << SVM_SELECTOR_P_SHIFT) #define SVM_SELECTOR_DB_MASK (1 << SVM_SELECTOR_DB_SHIFT) #define SVM_SELECTOR_G_MASK (1 << SVM_SELECTOR_G_SHIFT) enum { INTERCEPT_INTR, INTERCEPT_NMI, INTERCEPT_SMI, INTERCEPT_INIT, INTERCEPT_VINTR, INTERCEPT_SELECTIVE_CR0, INTERCEPT_STORE_IDTR, INTERCEPT_STORE_GDTR, INTERCEPT_STORE_LDTR, INTERCEPT_STORE_TR, INTERCEPT_LOAD_IDTR, INTERCEPT_LOAD_GDTR, INTERCEPT_LOAD_LDTR, INTERCEPT_LOAD_TR, INTERCEPT_RDTSC, INTERCEPT_RDPMC, INTERCEPT_PUSHF, INTERCEPT_POPF, INTERCEPT_CPUID, INTERCEPT_RSM, INTERCEPT_IRET, INTERCEPT_INTn, INTERCEPT_INVD, INTERCEPT_PAUSE, INTERCEPT_HLT, INTERCEPT_INVLPG, INTERCEPT_INVLPGA, INTERCEPT_IOIO_PROT, INTERCEPT_MSR_PROT, INTERCEPT_TASK_SWITCH, INTERCEPT_FERR_FREEZE, INTERCEPT_SHUTDOWN, INTERCEPT_VMRUN, INTERCEPT_VMMCALL, INTERCEPT_VMLOAD, INTERCEPT_VMSAVE, INTERCEP
- Severity from
- GitHub (reviewed advisory)