@@ -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);
@@ -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");
@@ -7,6 +7,7 @@ boot_idt = 0
.globl idt_descr
.globl tss_descr
.globl gdt64_desc
+.globl gdt64
ipi_vector = 0x20
@@ -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 *(%rax)\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,46 @@ static void trap_emulator(uint64_t *mem, void *alt_insn_page,
outregs = save;
}
+/*
+ * Trigger emulation of a compatibility mode instruction. Note that this
+ * function clobbers inregs.rax.
+ */
+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.rax = (uintptr_t)&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 +921,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 + sizeof(stack),
+ };
+ 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 +1269,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();