]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
x86: Intercept #AC and #DB to prevent guest-triggered microcode loops
authorJan Kiszka <jan.kiszka@siemens.com>
Mon, 21 Dec 2015 23:53:45 +0000 (00:53 +0100)
committerJan Kiszka <jan.kiszka@siemens.com>
Tue, 22 Dec 2015 14:24:11 +0000 (15:24 +0100)
This addresses CVE-2015-5307 and CVE-2015-8104 [1] for Jailhouse:
malicious cells may bring VCPUs into a state where the CPU will
infinitely loop over microcode, providing the hypervisor no chance to
interrupt these loops anymore. For this we have to intercept the #DB and
the exceptions to the cell.

If a guest is trapped in an exception loop can be detected by checking
the exception exit statistics which are now recorded: a large number of
exception exists per second (>1 million typically) will indicate this.

[1] http://permalink.gmane.org/gmane.comp.emulators.xen.user/85863

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
hypervisor/arch/x86/include/asm/processor.h
hypervisor/arch/x86/include/asm/svm.h
hypervisor/arch/x86/include/asm/vmx.h
hypervisor/arch/x86/svm.c
hypervisor/arch/x86/vmx.c

index 7dfc84615ff7efc7c79d8c4d38262035dc30b044..5ece2a6ba1c0dba72eb0158e9b7077400eca7d99 100644 (file)
 #define X86_OP_MOV_TO_MEM                              0x89
 #define X86_OP_MOV_FROM_MEM                            0x8b
 
+#define DB_VECTOR                                      1
 #define NMI_VECTOR                                     2
 #define PF_VECTOR                                      14
+#define AC_VECTOR                                      17
 
 #define DESC_TSS_BUSY                                  (1UL << (9 + 32))
 #define DESC_PRESENT                                   (1UL << (15 + 32))
index 5d1bd20b29dfb5178af9cca2becafdd42c6f8dc3..d5f2e1e40ec9f01ca574b081bc52bb4941616620 100644 (file)
 
 #define NPT_PAGE_DIR_LEVELS    4
 
+#define SVM_EVENTINJ_EXCEPTION (3UL << 8)
+#define SVM_EVENTINJ_ERR_VALID (1UL << 11)
+#define SVM_EVENTINJ_VALID     (1UL << 31)
+
 struct svm_segment {
        u16 selector;
        u16 access_rights;
@@ -264,7 +268,6 @@ enum clean_bits {
 };
 
 typedef u64 vintr_t;
-typedef u64 eventinj_t;
 typedef u64 lbrctrl_t;
 
 struct vmcb {
@@ -292,10 +295,11 @@ struct vmcb {
        u64 exitcode;                   /* offset 0x70 */
        u64 exitinfo1;                  /* offset 0x78 */
        u64 exitinfo2;                  /* offset 0x80 */
-       eventinj_t      exitintinfo;    /* offset 0x88 */
+       u64 exitintinfo;                /* offset 0x88 */
        u64 np_enable;                  /* offset 0x90 */
        u64 res08[2];
-       eventinj_t      eventinj;       /* offset 0xA8 */
+       u32 eventinj;                   /* offset 0xA8 */
+       u32 eventinj_err;               /* offset 0xAC */
        u64 n_cr3;                      /* offset 0xB0 */
        lbrctrl_t lbr_control;          /* offset 0xB8 */
        u64 clean_bits;                 /* offset 0xC0 */
index a2591ed516fa2e8168c8e33a2ac8592ec37f1585..775ef3f618f5e3e13c91078c37fa978bf607886b 100644 (file)
@@ -239,8 +239,13 @@ enum vmx_state { VMXOFF = 0, VMXON, VMCS_READY };
 
 #define VMX_MISC_ACTIVITY_HLT                  (1UL << 6)
 
+#define INTR_INFO_INTR_TYPE_MASK               BIT_MASK(10, 8)
 #define INTR_INFO_UNBLOCK_NMI                  (1UL << 12)
 
+#define INTR_TYPE_NMI_INTR                     (2UL << 8)
+
+#define INTR_TO_VECTORING_INFO_MASK            ((1UL << 31) | BIT_MASK(11, 0))
+
 #define EXIT_REASONS_FAILED_VMENTRY            (1UL << 31)
 
 #define EXIT_REASON_EXCEPTION_NMI              0
index d45627e922231dff05026a752061fd09fa4f5eb7..29292615c0c1da6eccf2ce7f5983df32b1a946a1 100644 (file)
@@ -211,6 +211,13 @@ static void vmcb_setup(struct per_cpu *cpu_data)
        vmcb->general2_intercepts |= GENERAL2_INTERCEPT_VMRUN; /* Required */
        vmcb->general2_intercepts |= GENERAL2_INTERCEPT_VMMCALL;
 
+       /*
+        * We only intercept #DB and #AC to prevent that malicious guests can
+        * trigger infinite loops in microcode (see e.g. CVE-2015-5307 and
+        * CVE-2015-8104).
+        */
+       vmcb->exception_intercepts |= (1 << DB_VECTOR) | (1 << AC_VECTOR);
+
        vmcb->msrpm_base_pa = paging_hvirt2phys(msrpm);
 
        vmcb->np_enable = 1;
@@ -579,6 +586,8 @@ void vcpu_vendor_reset(unsigned int sipi_vector)
 
        vmcb->dr7 = 0x00000400;
 
+       vmcb->eventinj = 0;
+
        /* Almost all of the guest state changed */
        vmcb->clean_bits = 0;
 
@@ -919,6 +928,18 @@ void vcpu_handle_exit(struct per_cpu *cpu_data)
                if (vcpu_handle_io_access())
                        goto vmentry;
                break;
+       case VMEXIT_EXCEPTION_DB:
+       case VMEXIT_EXCEPTION_AC:
+               cpu_data->stats[JAILHOUSE_CPU_STAT_VMEXITS_EXCEPTION]++;
+               /* Reinject exception, including error code if needed. */
+               vmcb->eventinj = (vmcb->exitcode - VMEXIT_EXCEPTION_DE) |
+                       SVM_EVENTINJ_EXCEPTION | SVM_EVENTINJ_VALID;
+               if (vmcb->exitcode == VMEXIT_EXCEPTION_AC) {
+                       vmcb->eventinj |= SVM_EVENTINJ_ERR_VALID;
+                       vmcb->eventinj_err = vmcb->exitinfo1;
+               }
+               x86_check_events();
+               goto vmentry;
        /* TODO: Handle VMEXIT_AVIC_NOACCEL and VMEXIT_AVIC_INCOMPLETE_IPI */
        default:
                panic_printk("FATAL: Unexpected #VMEXIT, exitcode %x, "
index ba0159c3f6a9632719bca18542258938277647fc..0aef3a1c1000cf9b1e55230c58cef0db4afbd039 100644 (file)
@@ -572,7 +572,9 @@ static bool vmcs_setup(struct per_cpu *cpu_data)
 
        ok &= vmx_set_cell_config();
 
-       ok &= vmcs_write32(EXCEPTION_BITMAP, 0);
+       /* see vmx_handle_exception_nmi for the interception reason */
+       ok &= vmcs_write32(EXCEPTION_BITMAP,
+                          (1 << DB_VECTOR) | (1 << AC_VECTOR));
 
        val = read_msr(MSR_IA32_VMX_EXIT_CTLS);
        val |= VM_EXIT_HOST_ADDR_SPACE_SIZE |
@@ -846,6 +848,7 @@ void vcpu_vendor_reset(unsigned int sipi_vector)
        ok &= vmcs_write32(GUEST_ACTIVITY_STATE, GUEST_ACTIVITY_ACTIVE);
        ok &= vmcs_write32(GUEST_INTERRUPTIBILITY_INFO, 0);
        ok &= vmcs_write64(GUEST_PENDING_DBG_EXCEPTIONS, 0);
+       ok &= vmcs_write32(VM_ENTRY_INTR_INFO_FIELD, 0);
 
        val = vmcs_read32(VM_ENTRY_CONTROLS);
        val &= ~VM_ENTRY_IA32E_MODE;
@@ -893,6 +896,34 @@ static void vmx_check_events(void)
        x86_check_events();
 }
 
+static void vmx_handle_exception_nmi(void)
+{
+       u32 intr_info = vmcs_read32(VM_EXIT_INTR_INFO);
+
+       if ((intr_info & INTR_INFO_INTR_TYPE_MASK) == INTR_TYPE_NMI_INTR) {
+               this_cpu_data()->stats[JAILHOUSE_CPU_STAT_VMEXITS_MANAGEMENT]++;
+               asm volatile("int %0" : : "i" (NMI_VECTOR));
+       } else {
+               this_cpu_data()->stats[JAILHOUSE_CPU_STAT_VMEXITS_EXCEPTION]++;
+               /*
+                * Reinject the event straight away. We only intercept #DB and
+                * #AC to prevent that malicious guests can trigger infinite
+                * loops in microcode (see e.g. CVE-2015-5307 and
+                * CVE-2015-8104).
+                */
+               vmcs_write32(VM_ENTRY_INTR_INFO_FIELD,
+                            intr_info & INTR_TO_VECTORING_INFO_MASK);
+               vmcs_write32(VM_ENTRY_EXCEPTION_ERROR_CODE,
+                            vmcs_read32(VM_EXIT_INTR_ERROR_CODE));
+       }
+
+       /*
+        * Check for events even in the exception case in order to maintain
+        * control over the guest if it triggered #DB or #AC loops.
+        */
+       vmx_check_events();
+}
+
 static void update_efer(void)
 {
        unsigned long efer = vmcs_read64(GUEST_IA32_EFER);
@@ -1056,8 +1087,8 @@ void vcpu_handle_exit(struct per_cpu *cpu_data)
 
        switch (reason) {
        case EXIT_REASON_EXCEPTION_NMI:
-               asm volatile("int %0" : : "i" (NMI_VECTOR));
-               /* fall through */
+               vmx_handle_exception_nmi();
+               return;
        case EXIT_REASON_PREEMPTION_TIMER:
                cpu_data->stats[JAILHOUSE_CPU_STAT_VMEXITS_MANAGEMENT]++;
                vmx_check_events();