Add VMware backdoors unit test. VMware backdoors, is a PV interface VMware ESX and Workstation expose to their running VMs, in order to better operate in a virtualized environment. There are 2 main backdoor mechanisms: I/O ports & Pseduo-PMCs. VMware backdoor I/O ports: VMware expose a special I/O port which sends/gives ParaVirtualize info. The I/O port is accessible from User Mode, even if the TSS I/O permission bitmap states otherwise. VMware backdoor Pseduo-PMCs: VMware exposes 3 Pseduo-PMCs to its running VMS, that lets the VMs collect information regarding host time. For more info, see: www.vmware.com/files/pdf/techpaper/Timekeeping-In-VirtualMachines.pdf This unit test tests the functionality of these backdoors. They are tested both from User Mode and Kernel Mode, as they will be accessible running on ESX. Signed-off-by: Arbel Moshe <arbel.moshe@xxxxxxxxxx> Reviewed-by: Liran Alon <liran.alon@xxxxxxxxxx> Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@xxxxxxxxxx> --- x86/Makefile.common | 1 + x86/unittests.cfg | 4 ++ x86/vmware_backdoors.c | 191 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 x86/vmware_backdoors.c diff --git a/x86/Makefile.common b/x86/Makefile.common index cbd5847..f544636 100644 --- a/x86/Makefile.common +++ b/x86/Makefile.common @@ -56,6 +56,7 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \ $(TEST_DIR)/init.flat $(TEST_DIR)/smap.flat \ $(TEST_DIR)/hyperv_synic.flat $(TEST_DIR)/hyperv_stimer.flat \ $(TEST_DIR)/hyperv_connections.flat \ + $(TEST_DIR)/vmware_backdoors.flat\ ifdef API tests-api = api/api-sample api/dirty-log api/dirty-log-perf diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 91d1c75..5410177 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -150,6 +150,10 @@ file = pmu.flat extra_params = -cpu host check = /proc/sys/kernel/nmi_watchdog=0 +[vmware_backdoors] +file = vmware_backdoors.flat +extra_params = -machine vmport=on + [port80] file = port80.flat diff --git a/x86/vmware_backdoors.c b/x86/vmware_backdoors.c new file mode 100644 index 0000000..f718f16 --- /dev/null +++ b/x86/vmware_backdoors.c @@ -0,0 +1,191 @@ + +#include "x86/msr.h" +#include "x86/processor.h" +#include "x86/apic-defs.h" +#include "x86/apic.h" +#include "x86/desc.h" +#include "x86/isr.h" +#include "alloc.h" +#include "setjmp.h" +#include "usermode.h" +#include "fault_test.h" + +#include "libcflat.h" +#include <stdint.h> + +#define VMWARE_BACKDOOR_PMC_HOST_TSC 0x10000 +#define VMWARE_BACKDOOR_PMC_REAL_TIME 0x10001 +#define VMWARE_BACKDOOR_PMC_APPARENT_TIME 0x10002 + +#define VMWARE_BACKDOOR_PORT 0x5658 +#define VMWARE_MAGIC 0x564D5868 + +#define VMPORT_CMD_GETVERSION 0x0a +#define VMPORT_CMD_ILLEGAL 0xfff + +#define VMPORT_DEFAULT_RETVAL 0xdeadbeef + +#define RANDOM_IO_PORT 0x1234 + +struct backdoor_port_result { + uint64_t rax; + uint64_t rbx; + uint64_t rcx; + uint64_t rdx; +}; + +static bool vmware_backdoor_port_callback(struct fault_test_arg *arg) +{ + struct backdoor_port_result *res = + (struct backdoor_port_result *) arg->retval; + + switch (arg->arg[2]) { + case VMPORT_CMD_GETVERSION: + return (res->rbx == VMWARE_MAGIC); + case VMPORT_CMD_ILLEGAL: + return (res->rbx == VMPORT_DEFAULT_RETVAL); + } + return false; +} + +static uint64_t vmware_backdoor_port(uint64_t vmport, uint64_t vmport_magic, + uint64_t command) +{ + struct backdoor_port_result *res = + (struct backdoor_port_result *) + malloc(sizeof(struct backdoor_port_result)); + + res->rax = VMPORT_DEFAULT_RETVAL; + res->rbx = VMPORT_DEFAULT_RETVAL; + res->rcx = VMPORT_DEFAULT_RETVAL; + res->rdx = VMPORT_DEFAULT_RETVAL; + + asm volatile( + "mov %[rax], %%rax\n\t" + "mov %[rdx], %%rdx\n\t" + "mov %[rcx], %%rcx\n\t" + "inl %%dx, %%eax\n\t" + : + "+a"(res->rax), + "+b"(res->rbx), + "+c"(res->rcx), + "+d"(res->rdx) + : + [rax]"m"(vmport_magic), + [rdx]"m"(vmport), + [rcx]"m"(command) + ); + + return (uint64_t) res; +} + +#define FAULT true +#define NO_FAULT false +#define USER_MODE true +#define KERNEL_MODE false + +#define RDPMC_ARG(n, m, sf) {.usermode = m, \ + .func = (test_fault_func) rdpmc, .fault_vector = GP_VECTOR, \ + .should_fault = sf, .arg = {n, 0, 0, 0}, .callback = NULL} + +#define RDPMC_TEST(name, a, m, sf) FAULT_TEST("rdpmc_test: "name, \ + RDPMC_ARG(a, m, sf)) + +#define PORT_ARG(a, b, c, m, sf) {.usermode = m, \ + .func = (test_fault_func) vmware_backdoor_port, \ + .fault_vector = GP_VECTOR, .should_fault = sf, .arg = {a, b, c, 0}, \ + .callback = vmware_backdoor_port_callback} + +#define PORT_TEST(name, a, b, c, m, sf) FAULT_TEST("port_test: "name, \ + PORT_ARG(a, b, c, m, sf)) + + +struct fault_test vmware_backdoor_tests[] = { + RDPMC_TEST("HOST_TSC kernel", VMWARE_BACKDOOR_PMC_HOST_TSC, + KERNEL_MODE, NO_FAULT), + RDPMC_TEST("REAL_TIME kernel", VMWARE_BACKDOOR_PMC_REAL_TIME, + KERNEL_MODE, NO_FAULT), + RDPMC_TEST("APPARENT_TIME kernel", VMWARE_BACKDOOR_PMC_APPARENT_TIME, + KERNEL_MODE, NO_FAULT), + RDPMC_TEST("HOST_TSC user", VMWARE_BACKDOOR_PMC_HOST_TSC, + USER_MODE, NO_FAULT), + RDPMC_TEST("REAL_TIME user", VMWARE_BACKDOOR_PMC_REAL_TIME, + USER_MODE, NO_FAULT), + RDPMC_TEST("APPARENT_TIME user", VMWARE_BACKDOOR_PMC_APPARENT_TIME, + USER_MODE, NO_FAULT), + RDPMC_TEST("RANDOM PMC user", 0xfff, USER_MODE, FAULT), + + PORT_TEST("CMD_GETVERSION user", VMWARE_BACKDOOR_PORT, VMWARE_MAGIC, + VMPORT_CMD_GETVERSION, USER_MODE, NO_FAULT), + PORT_TEST("CMD_GETVERSION kernel", VMWARE_BACKDOOR_PORT, VMWARE_MAGIC, + VMPORT_CMD_GETVERSION, KERNEL_MODE, NO_FAULT), + PORT_TEST("CMD_ILLEGAL user", VMWARE_BACKDOOR_PORT, VMWARE_MAGIC, + VMPORT_CMD_ILLEGAL, USER_MODE, NO_FAULT), + PORT_TEST("RANDOM port user", RANDOM_IO_PORT, VMWARE_MAGIC, 0xfff, + USER_MODE, FAULT), + { NULL }, +}; + +/* + * Set TSS IO Perm to throw GP on RANDOM_IO_PORT and VMWARE_BACKDOOR_PORT + * from User Mode + */ +static void set_tss_ioperm(void) +{ + struct descriptor_table_ptr gdt; + struct segment_desc64 *gdt_table; + struct segment_desc64 *tss_entry; + u16 tr = 0; + tss64_t *tss; + unsigned char *ioperm_bitmap; + uint64_t tss_base; + + sgdt(&gdt); + tr = str(); + gdt_table = (struct segment_desc64 *) gdt.base; + tss_entry = &gdt_table[tr / sizeof(struct segment_desc64)]; + tss_base = ((uint64_t) tss_entry->base1 | + ((uint64_t) tss_entry->base2 << 16) | + ((uint64_t) tss_entry->base3 << 24) | + ((uint64_t) tss_entry->base4 << 32)); + tss = (tss64_t *)tss_base; + tss->iomap_base = sizeof(*tss); + ioperm_bitmap = ((unsigned char *)tss+tss->iomap_base); + + /* We want GP on RANDOM_IO_PORT and VMWARE_BACKDOOR_PORT */ + ioperm_bitmap[RANDOM_IO_PORT / 8] |= + 1 << (RANDOM_IO_PORT % 8); + ioperm_bitmap[VMWARE_BACKDOOR_PORT / 8] |= + 1 << (VMWARE_BACKDOOR_PORT % 8); + *(uint64_t *)tss_entry &= ~DESC_BUSY; + + /* Update TSS */ + ltr(tr); +} + +static void check_vmware_backdoors(void) +{ + int i; + + /* Disable Permissions for IO PORTS */ + set_tss_ioperm(); + /* Disable Permission to run rdpmc from user mode */ + write_cr4(read_cr4() & ~X86_CR4_PCE); + + report_prefix_push("vmware_backdoors"); + + for (i = 0; vmware_backdoor_tests[i].name != NULL; i++) + test_run(&vmware_backdoor_tests[i]); + + report_prefix_pop(); +} + +int main(int ac, char **av) +{ + setup_vm(); + setup_idt(); + + check_vmware_backdoors(); + + return report_summary(); +} -- 2.14.1