em_jmp_far and em_ret_far assumed that setting IP can only fail in 64 bit mode, but syzkaller proved otherwise (and SDM agrees). This test exercises the bug. Signed-off-by: Jim Mattson <jmattson@xxxxxxxxxx> --- lib/x86/desc.h | 1 + lib/x86/processor.h | 5 +++ x86/cstart64.S | 1 + x86/emulator.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/lib/x86/desc.h b/lib/x86/desc.h index be52fd4..17c7960 100644 --- a/lib/x86/desc.h +++ b/lib/x86/desc.h @@ -142,6 +142,7 @@ void set_intr_task_gate(int vec, void *fn); void setup_tss32(void); #else extern tss64_t tss; +extern gdt_entry_t gdt64[]; #endif unsigned exception_vector(void); diff --git a/lib/x86/processor.h b/lib/x86/processor.h index ee7f180..6cb7937 100644 --- a/lib/x86/processor.h +++ b/lib/x86/processor.h @@ -46,6 +46,11 @@ struct descriptor_table_ptr { ulong base; } __attribute__((packed)); +struct far_pointer32 { + u32 offset; + u16 selector; +} __attribute__((packed)); + static inline void barrier(void) { asm volatile ("" : : : "memory"); diff --git a/x86/cstart64.S b/x86/cstart64.S index e947888..8bb7af3 100644 --- a/x86/cstart64.S +++ b/x86/cstart64.S @@ -7,6 +7,7 @@ boot_idt = 0 .globl idt_descr .globl tss_descr .globl gdt64_desc +.globl gdt64 ipi_vector = 0x20 diff --git a/x86/emulator.c b/x86/emulator.c index 8d262d8..ca7dc37 100644 --- a/x86/emulator.c +++ b/x86/emulator.c @@ -768,6 +768,32 @@ asm( ".align 4096\n\t" ); +asm( + ".align 4096\n\t" + "compat_insn_page:\n\t" + "ret\n\t" + "pushf\n\t" + "push 136+save\n\t" + "popf\n\t" + INSN_XCHG_ALL + "ljmp *(%rsp)\n\t" + ".code32\n\t" + "compat_test_insn:\n\t" + "in (%dx), %al\n\t" + ".skip 31, 0x90\n\t" + "compat_test_insn_end:\n\t" + "ljmp $" xstr(KERNEL_CS) ", $64f\n\t" + ".code64\n\t" + "64:\n\t" + INSN_XCHG_ALL + "pushf\n\t" + "pop 136+save\n\t" + "popf\n\t" + "ret\n\t" + "compat_insn_page_end:\n\t" + ".align 4096\n\t" +); + #define MK_INSN(name, str) \ asm ( \ ".pushsection .data.insn \n\t" \ @@ -810,6 +836,48 @@ static void trap_emulator(uint64_t *mem, void *alt_insn_page, outregs = save; } +/* + * Trigger emulation of a compatibility mode instruction. Note that this + * function requires inregs.rsp to be a valid stack address with room for + * a 32-bit far-pointer to be pushed. + */ +static void trap_emulator32(uint64_t *mem, void *alt_insn_page, + struct insn_desc *alt_insn) +{ + extern u8 compat_insn_page[], compat_test_insn[]; + ulong *cr3 = (ulong *)read_cr3(); + struct far_pointer32 compat_fp = { + .offset = (uintptr_t)compat_test_insn, + .selector = KERNEL_CS32, + }; + void *insn_ram; + + insn_ram = compat_insn_page; + memcpy(alt_insn_page, compat_insn_page, 4096); + memcpy(alt_insn_page + (compat_test_insn - compat_insn_page), + (void *)(alt_insn->ptr), alt_insn->len); + save = inregs; + save.rsp -= ((sizeof(compat_fp) + 15) / 16) * 16; + memcpy((void *)save.rsp, &compat_fp, sizeof(compat_fp)); + + /* + * Load the code TLB with compat_insn_page, but point the page + * tables at alt_insn_page (and keep the data TLB clear, for + * AMD decode assist). This will make the CPU trap on the + * compat_insn_page instruction but the hypervisor will see + * alt_insn_page. + */ + install_page(cr3, virt_to_phys(compat_insn_page), insn_ram); + invlpg(insn_ram); + /* Load code TLB */ + asm volatile("call *%0" : : "r" (insn_ram)); + install_page(cr3, virt_to_phys(alt_insn_page), insn_ram); + /* Trap, let hypervisor emulate at alt_insn_page */ + asm volatile("call *%0" : : "r" (insn_ram + 1)); + + outregs = save; +} + static unsigned long rip_advance; static void advance_rip_and_note_exception(struct ex_regs *regs) @@ -855,6 +923,47 @@ static void test_jmp_noncanonical(uint64_t *mem) handle_exception(GP_VECTOR, 0); } +u16 exception_cs; + +static void record_cs_and_advance_rip(struct ex_regs *regs) +{ + exception_cs = regs->cs; + regs->rip += rip_advance; +} + +/* + * This test ensures that emulation of 32-bit far jmp works properly + * when the far jmp raises #GP because the offset in the target operand + * is beyond the limit of the code segment in the target operand. + */ +static void test_jmpfar_32bitgp(uint64_t *mem, uint8_t *insn_page, + uint8_t *alt_insn_page, void *insn_ram) +{ + void *stack = alloc_page(); + gdt_entry_t *spare = gdt64 + (FIRST_SPARE_SEL >> 3); + struct far_pointer32 spare_fp = { + .offset = 0x1000, + .selector = FIRST_SPARE_SEL, + }; + /* + * Set up a 32-bit code segment with a single-page limit. + */ + *spare = gdt64[KERNEL_CS32 >> 3]; + spare->limit_low = 0; + spare->granularity &= ~0xf; + + handle_exception(GP_VECTOR, record_cs_and_advance_rip); + MK_INSN(jmp_far32, ".code32; ljmp *(%edx); .code64"); + exception_cs = -1; + rip_advance = insn_jmp_far32.len; + inregs = (struct regs){ .rdx = (uintptr_t)&spare_fp, + .rsp = (uintptr_t)stack + PAGE_SIZE, + }; + trap_emulator32(mem, alt_insn_page, &insn_jmp_far32); + report("32-bit far jmp #GP preserves CS", exception_cs == KERNEL_CS32); + handle_exception(GP_VECTOR, 0); +} + static void test_movabs(uint64_t *mem, uint8_t *insn_page, uint8_t *alt_insn_page, void *insn_ram) { @@ -1162,6 +1271,7 @@ int main() test_string_io_mmio(mem); test_jmp_noncanonical(mem); + test_jmpfar_32bitgp(mem, insn_page, alt_insn_page, insn_ram); test_illegal_movbe(); return report_summary(); -- 2.8.0.rc3.226.g39d4020 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html