Skip to content
This repository was archived by the owner on Jan 28, 2023. It is now read-only.

Commit 50b6806

Browse files
committed
core: fix bug that INVEPT is not invoked on Mac
There is coexistence issue in MacOS when some Mac VMMs are running at the same time. invept might not be invoked due to coexistence issue. This change is to fix the issue that invept is not invoked. It also refactors the existed logic for VMXON/VMXOFF into two functions. The function names come from PR #49.
1 parent 3228f29 commit 50b6806

File tree

3 files changed

+126
-119
lines changed

3 files changed

+126
-119
lines changed

core/cpu.c

Lines changed: 121 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -567,8 +567,6 @@ uint32 load_vmcs(struct vcpu_t *vcpu, preempt_flag *flags)
567567
struct per_cpu_data *cpu_data;
568568
paddr_t vmcs_phy;
569569
paddr_t curr_vmcs = VMCS_NONE;
570-
vmx_error_t err = 0;
571-
uint64 fc_msr;
572570

573571
hax_disable_preemption(flags);
574572

@@ -584,87 +582,9 @@ uint32 load_vmcs(struct vcpu_t *vcpu, preempt_flag *flags)
584582
return 0;
585583
}
586584

587-
cpu_data->host_cr4_vmxe = (get_cr4() & CR4_VMXE);
588-
if(cpu_data->host_cr4_vmxe) {
589-
if (debug_vmcs_count % 100000 == 0) {
590-
hax_debug("host VT has enabled!\n");
591-
hax_debug("Cr4 value = 0x%lx\n", get_cr4());
592-
log_host_cr4_vmxe = 1;
593-
log_host_cr4 = get_cr4();
594-
}
595-
debug_vmcs_count++;
596-
}
597-
set_cr4(get_cr4() | CR4_VMXE);
598-
/* HP systems & Mac systems workaround
599-
* When resuming from S3, some HP/Mac set the IA32_FEATURE_CONTROL MSR to
600-
* zero. Setting the lock bit to zero & then doing 'vmxon' would cause a GP.
601-
* As a workaround, when we see this condition, we enable the bits so that
602-
* we can launch vmxon & thereby hax.
603-
* bit 0 - Lock bit
604-
* bit 2 - Enable VMX outside SMX operation
605-
*
606-
* ********* To Do **************************************
607-
* This is the workground to fix BSOD when resume from S3
608-
* The best way is to add one power management handler, and set
609-
* IA32_FEATURE_CONTROL MSR in that PM S3 handler
610-
* *****************************************************
611-
*/
612-
fc_msr = ia32_rdmsr(IA32_FEATURE_CONTROL);
613-
if (!(fc_msr & FC_LOCKED))
614-
ia32_wrmsr(IA32_FEATURE_CONTROL,
615-
fc_msr | FC_LOCKED | FC_VMXON_OUTSMX);
616-
617-
err = __vmxon(hax_page_pa(cpu_data->vmxon_page));
618-
619-
log_vmxon_err = err;
620-
log_vmxon_addr = hax_page_pa(cpu_data->vmxon_page);
621-
622-
if (!(err & VMX_FAIL_MASK))
623-
cpu_data->vmm_flag |= VMXON_HAX;
624-
else {
625-
bool fatal = true;
626-
627-
#ifdef __MACH__
628-
if ((err & VMX_FAIL_INVALID) && cpu_data->host_cr4_vmxe) {
629-
// On macOS, if VMXON fails with VMX_FAIL_INVALID and host CR4.VMXE
630-
// was already set, it is very likely that another VMM (VirtualBox
631-
// or any VMM based on macOS Hypervisor Framework, e.g. Docker) is
632-
// running and did not call VMXOFF. In that case, the current host
633-
// logical processor is already in VMX operation, and we can use an
634-
// innocuous VMX instruction (VMPTRST) to confirm that.
635-
// However, if the above assumption is wrong and the host processor
636-
// is not actually in VMX operation, VMPTRST will probably cause a
637-
// host reboot. But we don't have a better choice, and it is worth
638-
// taking the risk.
639-
curr_vmcs = __vmptrst();
640-
if (curr_vmcs == VMCS_NONE) {
641-
hax_debug("Already in VMX operation, courtesy of another"
642-
" VMM (VirtualBox or macOS Hypervisor Framework)\n");
643-
fatal = false;
644-
// Indicate that it is not necessary to call VMXOFF later
645-
cpu_data->vmm_flag &= ~VMXON_HAX;
646-
} else {
647-
// Should never happen
648-
hax_error("VMXON failed with VMX_FAIL_INVALID, but there is a"
649-
" current VMCS at 0x%llx\n", curr_vmcs);
650-
}
651-
}
652-
#endif
653-
654-
if (fatal) {
655-
hax_error("VMXON failed for region 0x%llx (err=0x%x)\n",
656-
hax_page_pa(cpu_data->vmxon_page), (uint32) err);
657-
restore_host_cr4_vmxe(cpu_data);
658-
if (err & VMX_FAIL_INVALID) {
659-
log_vmxon_err_type1 = 1;
660-
} else {
661-
// TODO: Should VMX_FAIL_VALID be ignored? The current VMCS can
662-
// be cleared (deactivated and saved to memory) using VMCLEAR
663-
log_vmxon_err_type2 = 1;
664-
}
665-
hax_enable_preemption(flags);
666-
return VMXON_FAIL;
667-
}
585+
if (cpu_vmxroot_enter() != VMX_SUCCEED) {
586+
hax_enable_preemption(flags);
587+
return VMXON_FAIL;
668588
}
669589

670590
if (vcpu)
@@ -679,9 +599,7 @@ uint32 load_vmcs(struct vcpu_t *vcpu, preempt_flag *flags)
679599

680600
if (__vmptrld(vmcs_phy) != VMX_SUCCEED) {
681601
hax_error("HAX: vmptrld failed (%08llx)\n", vmcs_phy);
682-
cpu_data->vmm_flag = 0;
683-
__vmxoff();
684-
restore_host_cr4_vmxe(cpu_data);
602+
cpu_vmxroot_leave();
685603
log_vmxon_err_type3 = 1;
686604
hax_enable_preemption(flags);
687605
return VMPTRLD_FAIL;
@@ -716,7 +634,6 @@ uint32 put_vmcs(struct vcpu_t *vcpu, preempt_flag *flags)
716634
int cpu_id = hax_cpuid();
717635
struct per_cpu_data *cpu_data = hax_cpu_data[cpu_id];
718636
paddr_t vmcs_phy;
719-
vmx_error_t err = 0;
720637
vmx_error_t vmxoff_err = 0;
721638
if (vcpu && cpu_data->nested > 0) {
722639
cpu_data->nested--;
@@ -735,27 +652,8 @@ uint32 put_vmcs(struct vcpu_t *vcpu, preempt_flag *flags)
735652

736653
cpu_data->current_vcpu = NULL;
737654

738-
if (cpu_data->vmm_flag & VMXON_HAX) {
739-
err = __vmxoff();
740-
if (!(err & VMX_FAIL_MASK)) {
741-
restore_host_cr4_vmxe(cpu_data);
742-
} else {
743-
hax_error("VMXOFF Failed..........\n");
744-
vmxoff_err = err;
745-
log_vmxoff_err = err;
746-
}
747-
} else {
748-
log_vmxoff_no = 1;
749-
#ifdef __MACH__
750-
hax_debug("Skipping VMXOFF because another VMM (VirtualBox or macOS"
751-
" Hypervisor Framework) is running\n");
752-
#else
753-
vmxoff_err = 0x1;
754-
hax_error("NO VMXOFF.......\n");
755-
#endif
756-
}
655+
vmxoff_err = cpu_vmxroot_leave();
757656
cpu_data->other_vmcs = VMCS_NONE;
758-
cpu_data->vmm_flag = 0;
759657
if (vcpu && vcpu->is_vmcs_loaded)
760658
vcpu->is_vmcs_loaded = 0;
761659
out:
@@ -817,3 +715,119 @@ static vmx_error_t cpu_vmentry_failed(struct vcpu_t *vcpu, vmx_error_t err)
817715
hax_log("end of cpu_vmentry_failed\n");
818716
return err;
819717
}
718+
719+
vmx_error_t cpu_vmxroot_leave(void)
720+
{
721+
struct per_cpu_data *cpu_data = current_cpu_data();
722+
vmx_error_t err = VMX_SUCCEED;
723+
724+
if (cpu_data->vmm_flag & VMXON_HAX) {
725+
err = __vmxoff();
726+
if (!(err & VMX_FAIL_MASK)) {
727+
cpu_data->vmm_flag &= ~VMXON_HAX;
728+
restore_host_cr4_vmxe(cpu_data);
729+
} else {
730+
hax_error("VMXOFF Failed..........\n");
731+
}
732+
} else {
733+
log_vmxoff_no = 1;
734+
#ifdef __MACH__
735+
hax_debug("Skipping VMXOFF because another VMM (VirtualBox or macOS"
736+
" Hypervisor Framework) is running\n");
737+
#else
738+
// It should not go here in Win64/win32
739+
err = VMX_FAIL_VALID;
740+
hax_error("NO VMXOFF.......\n");
741+
#endif
742+
}
743+
cpu_data->vmxoff_err = err;
744+
745+
return err;
746+
}
747+
748+
vmx_error_t cpu_vmxroot_enter(void)
749+
{
750+
struct per_cpu_data *cpu_data = current_cpu_data();
751+
uint64 fc_msr;
752+
vmx_error_t err = VMX_SUCCEED;
753+
754+
cpu_data->host_cr4_vmxe = (get_cr4() & CR4_VMXE);
755+
if (cpu_data->host_cr4_vmxe) {
756+
if (debug_vmcs_count % 100000 == 0) {
757+
hax_debug("host VT has enabled!\n");
758+
hax_debug("Cr4 value = 0x%lx\n", get_cr4());
759+
log_host_cr4_vmxe = 1;
760+
log_host_cr4 = get_cr4();
761+
}
762+
debug_vmcs_count++;
763+
}
764+
765+
set_cr4(get_cr4() | CR4_VMXE);
766+
/* HP systems & Mac systems workaround
767+
* When resuming from S3, some HP/Mac set the IA32_FEATURE_CONTROL MSR to
768+
* zero. Setting the lock bit to zero & then doing 'vmxon' would cause a GP.
769+
* As a workaround, when we see this condition, we enable the bits so that
770+
* we can launch vmxon & thereby hax.
771+
* bit 0 - Lock bit
772+
* bit 2 - Enable VMX outside SMX operation
773+
*
774+
* ********* To Do **************************************
775+
* This is the workground to fix BSOD when resume from S3
776+
* The best way is to add one power management handler, and set
777+
* IA32_FEATURE_CONTROL MSR in that PM S3 handler
778+
* *****************************************************
779+
*/
780+
fc_msr = ia32_rdmsr(IA32_FEATURE_CONTROL);
781+
if (!(fc_msr & FC_LOCKED))
782+
ia32_wrmsr(IA32_FEATURE_CONTROL,
783+
fc_msr | FC_LOCKED | FC_VMXON_OUTSMX);
784+
785+
err = __vmxon(hax_page_pa(cpu_data->vmxon_page));
786+
787+
log_vmxon_err = err;
788+
log_vmxon_addr = hax_page_pa(cpu_data->vmxon_page);
789+
790+
if (!(err & VMX_FAIL_MASK)) {
791+
cpu_data->vmm_flag |= VMXON_HAX;
792+
} else {
793+
bool fatal = true;
794+
795+
#ifdef __MACH__
796+
if ((err & VMX_FAIL_INVALID) && cpu_data->host_cr4_vmxe) {
797+
// On macOS, if VMXON fails with VMX_FAIL_INVALID and host CR4.VMXE
798+
// was already set, it is very likely that another VMM (VirtualBox
799+
// or any VMM based on macOS Hypervisor Framework, e.g. Docker) is
800+
// running and did not call VMXOFF. In that case, the current host
801+
// logical processor is already in VMX operation, and we can use an
802+
// innocuous VMX instruction (VMPTRST) to confirm that.
803+
// However, if the above assumption is wrong and the host processor
804+
// is not actually in VMX operation, VMPTRST will probably cause a
805+
// host reboot. But we don't have a better choice, and it is worth
806+
// taking the risk.
807+
__vmptrst();
808+
809+
// It is still alive - Just assumption is right.
810+
fatal = false;
811+
err = VMX_SUCCEED;
812+
// Indicate that it is not necessary to call VMXOFF later
813+
cpu_data->vmm_flag &= ~VMXON_HAX;
814+
}
815+
#endif
816+
817+
if (fatal) {
818+
hax_error("VMXON failed for region 0x%llx (err=0x%x, vmxe=%x)\n",
819+
hax_page_pa(cpu_data->vmxon_page), (uint32)err,
820+
(uint32)cpu_data->host_cr4_vmxe);
821+
restore_host_cr4_vmxe(cpu_data);
822+
if (err & VMX_FAIL_INVALID) {
823+
log_vmxon_err_type1 = 1;
824+
} else {
825+
// TODO: Should VMX_FAIL_VALID be ignored? The current VMCS can
826+
// be cleared (deactivated and saved to memory) using VMCLEAR
827+
log_vmxon_err_type2 = 1;
828+
}
829+
}
830+
}
831+
cpu_data->vmxon_err = err;
832+
return err;
833+
}

core/ept.c

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -330,22 +330,13 @@ static void invept_smpfunc(struct invept_bundle *bundle)
330330

331331
smp_mb();
332332
cpu_data = current_cpu_data();
333-
cpu_data->vmxon_err = VMX_SUCCEED;
334-
cpu_data->vmxoff_err = VMX_SUCCEED;
335333
cpu_data->invept_err = VMX_SUCCEED;
336334

337-
cpu_data->host_cr4_vmxe = get_cr4() & CR4_VMXE;
338-
set_cr4(get_cr4() | CR4_VMXE);
339-
cpu_data->vmxon_err = __vmxon(hax_page_pa(cpu_data->vmxon_page));
335+
cpu_vmxroot_enter();
340336

341337
if (!(cpu_data->vmxon_err & VMX_FAIL_MASK)) {
342338
cpu_data->invept_err = __invept(bundle->type, bundle->desc);
343-
cpu_data->vmxoff_err = __vmxoff();
344-
if (cpu_data->host_cr4_vmxe) {
345-
set_cr4(get_cr4() | CR4_VMXE);
346-
} else {
347-
set_cr4(get_cr4() & ~CR4_VMXE);
348-
}
339+
cpu_vmxroot_leave();
349340
}
350341
}
351342

core/include/cpu.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ static vmcs_t * current_cpu_vmcs(void)
165165

166166
void cpu_init_vmx(void *arg);
167167
void cpu_exit_vmx(void *arg);
168-
void cpu_enter_vmx(void *arg);
169168

170169
void cpu_pmu_init(void *arg);
171170

@@ -182,6 +181,9 @@ vmx_error_t vmptrld(paddr_t vmcs, struct vcpu_t *vcpu);
182181
vmx_error_t resume(paddr_t vmcs, struct vcpu_t *vcpu);
183182
vmx_error_t launch(paddr_t vmcs, struct vcpu_t *vcpu);
184183

184+
vmx_error_t cpu_vmxroot_leave(void);
185+
vmx_error_t cpu_vmxroot_enter(void);
186+
185187
extern struct hax_page *io_bitmap_page_a;
186188
extern struct hax_page *io_bitmap_page_b;
187189
extern struct hax_page *msr_bitmap_page;

0 commit comments

Comments
 (0)