x86emul: support LAR/LSL/VERR/VERW This involves protmode_load_seg() accepting x86_seg_none as input, with the meaning to - suppress any exceptions other than #PF, - not commit any state. Signed-off-by: Jan Beulich --- v3: Re-base. v2: Extend commit message and add a respective code comment. Add ASSERT()s to ensure/document that only #PF can make it out of protmode_load_seg(). --- a/tools/tests/x86_emulator/test_x86_emulator.c +++ b/tools/tests/x86_emulator/test_x86_emulator.c @@ -44,7 +44,47 @@ static int read( if ( verbose ) printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes); - bytes_read += bytes; + switch ( seg ) + { + uint64_t value; + + case x86_seg_gdtr: + /* Fake system segment type matching table index. */ + if ( (offset & 7) || (bytes > 8) ) + return X86EMUL_UNHANDLEABLE; +#ifdef __x86_64__ + if ( !(offset & 8) ) + { + memset(p_data, 0, bytes); + return X86EMUL_OKAY; + } + value = (offset - 8) >> 4; +#else + value = (offset - 8) >> 3; +#endif + if ( value >= 0x10 ) + return X86EMUL_UNHANDLEABLE; + value |= value << 40; + memcpy(p_data, &value, bytes); + return X86EMUL_OKAY; + + case x86_seg_ldtr: + /* Fake user segment type matching table index. */ + if ( (offset & 7) || (bytes > 8) ) + return X86EMUL_UNHANDLEABLE; + value = offset >> 3; + if ( value >= 0x10 ) + return X86EMUL_UNHANDLEABLE; + value |= (value | 0x10) << 40; + memcpy(p_data, &value, bytes); + return X86EMUL_OKAY; + + default: + if ( !is_x86_user_segment(seg) ) + return X86EMUL_UNHANDLEABLE; + bytes_read += bytes; + break; + } memcpy(p_data, (void *)offset, bytes); return X86EMUL_OKAY; } @@ -73,6 +113,8 @@ static int write( if ( verbose ) printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes); + if ( !is_x86_user_segment(seg) ) + return X86EMUL_UNHANDLEABLE; memcpy((void *)offset, p_data, bytes); return X86EMUL_OKAY; } @@ -88,17 +130,48 @@ static int cmpxchg( if ( verbose ) printf("** %s(%u, %p,, %u,)\n", __func__, seg, (void *)offset, bytes); + if ( !is_x86_user_segment(seg) ) + return X86EMUL_UNHANDLEABLE; memcpy((void *)offset, new, bytes); return X86EMUL_OKAY; } +static int read_segment( + enum x86_segment seg, + struct segment_register *reg, + struct x86_emulate_ctxt *ctxt) +{ + if ( !is_x86_user_segment(seg) ) + return X86EMUL_UNHANDLEABLE; + memset(reg, 0, sizeof(*reg)); + reg->attr.fields.p = 1; + return X86EMUL_OKAY; +} + +static int read_msr( + unsigned int reg, + uint64_t *val, + struct x86_emulate_ctxt *ctxt) +{ + switch ( reg ) + { + case 0xc0000080: /* EFER */ + *val = ctxt->addr_size > 32 ? 0x500 /* LME|LMA */ : 0; + return X86EMUL_OKAY; + } + + return X86EMUL_UNHANDLEABLE; +} + static struct x86_emulate_ops emulops = { .read = read, .insn_fetch = fetch, .write = write, .cmpxchg = cmpxchg, + .read_segment = read_segment, .cpuid = emul_test_cpuid, .read_cr = emul_test_read_cr, + .read_msr = read_msr, .get_fpu = emul_test_get_fpu, }; @@ -611,6 +684,156 @@ int main(int argc, char **argv) goto fail; printf("okay\n"); + printf("%-40s", "Testing lar (null selector)..."); + instr[0] = 0x0f; instr[1] = 0x02; instr[2] = 0xc1; + regs.eflags = 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.ecx = 0; + regs.eax = 0x11111111; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.eax != 0x11111111) || + (regs.eflags != 0x200) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + printf("okay\n"); + + printf("%-40s", "Testing lsl (null selector)..."); + instr[0] = 0x0f; instr[1] = 0x03; instr[2] = 0xca; + regs.eflags = 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.edx = 0; + regs.ecx = 0x11111111; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.ecx != 0x11111111) || + (regs.eflags != 0x200) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + printf("okay\n"); + + printf("%-40s", "Testing verr (null selector)..."); + instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0x21; + regs.eflags = 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.ecx = (unsigned long)res; + *res = 0; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.eflags != 0x200) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + printf("okay\n"); + + printf("%-40s", "Testing verw (null selector)..."); + instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0x2a; + regs.eflags = 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.ecx = 0; + regs.edx = (unsigned long)res; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.eflags != 0x200) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + printf("okay\n"); + + printf("%-40s", "Testing lar/lsl/verr/verw (all types)..."); + for ( i = 0; i < 0x20; ++i ) + { + unsigned int sel = i < 0x10 ? +#ifndef __x86_64__ + (i << 3) + 8 +#else + (i << 4) + 8 +#endif + : ((i - 0x10) << 3) | 4; + bool failed; + +#ifndef __x86_64__ +# define LAR_VALID 0xffff1a3eU +# define LSL_VALID 0xffff0a0eU +#else +# define LAR_VALID 0xffff1a04U +# define LSL_VALID 0xffff0a04U +#endif +#define VERR_VALID 0xccff0000U +#define VERW_VALID 0x00cc0000U + + instr[0] = 0x0f; instr[1] = 0x02; instr[2] = 0xc2; + regs.eflags = (LAR_VALID >> i) & 1 ? 0x200 : 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.edx = sel; + regs.eax = 0x11111111; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + if ( (LAR_VALID >> i) & 1 ) + failed = (regs.eflags != 0x240) || + ((regs.eax & 0xf0ff00) != (i << 8)); + else + failed = (regs.eflags != 0x200) || + (regs.eax != 0x11111111); + if ( failed ) + { + printf("LAR %04x (type %02x) ", sel, i); + goto fail; + } + + instr[0] = 0x0f; instr[1] = 0x03; instr[2] = 0xd1; + regs.eflags = (LSL_VALID >> i) & 1 ? 0x200 : 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.ecx = sel; + regs.edx = 0x11111111; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + if ( (LSL_VALID >> i) & 1 ) + failed = (regs.eflags != 0x240) || + (regs.edx != (i & 0xf)); + else + failed = (regs.eflags != 0x200) || + (regs.edx != 0x11111111); + if ( failed ) + { + printf("LSL %04x (type %02x) ", sel, i); + goto fail; + } + + instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0xe2; + regs.eflags = (VERR_VALID >> i) & 1 ? 0x200 : 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.ecx = 0; + regs.edx = sel; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + if ( regs.eflags != ((VERR_VALID >> i) & 1 ? 0x240 : 0x200) ) + { + printf("VERR %04x (type %02x) ", sel, i); + goto fail; + } + + instr[0] = 0x0f; instr[1] = 0x00; instr[2] = 0xe9; + regs.eflags = (VERW_VALID >> i) & 1 ? 0x200 : 0x240; + regs.eip = (unsigned long)&instr[0]; + regs.ecx = sel; + regs.edx = 0; + rc = x86_emulate(&ctxt, &emulops); + if ( (rc != X86EMUL_OKAY) || + (regs.eip != (unsigned long)&instr[3]) ) + goto fail; + if ( regs.eflags != ((VERW_VALID >> i) & 1 ? 0x240 : 0x200) ) + { + printf("VERW %04x (type %02x) ", sel, i); + goto fail; + } + } + printf("okay\n"); + #define decl_insn(which) extern const unsigned char which[], which##_len[] #define put_insn(which, insn) ".pushsection .test, \"ax\", @progbits\n" \ #which ": " insn "\n" \ --- a/xen/arch/x86/x86_emulate/x86_emulate.c +++ b/xen/arch/x86/x86_emulate/x86_emulate.c @@ -182,7 +182,7 @@ static const opcode_desc_t opcode_table[ static const opcode_desc_t twobyte_table[256] = { /* 0x00 - 0x07 */ - ModRM, ImplicitOps|ModRM, ModRM, ModRM, + ModRM, ImplicitOps|ModRM, DstReg|SrcMem16|ModRM, DstReg|SrcMem16|ModRM, 0, ImplicitOps, ImplicitOps, ImplicitOps, /* 0x08 - 0x0F */ ImplicitOps, ImplicitOps, 0, ImplicitOps, @@ -1365,6 +1365,11 @@ realmode_load_seg( return rc; } +/* + * Passing in x86_seg_none means + * - suppress any exceptions other than #PF, + * - don't commit any state. + */ static int protmode_load_seg( enum x86_segment seg, @@ -1407,7 +1412,7 @@ protmode_load_seg( } /* System segment descriptors must reside in the GDT. */ - if ( !is_x86_user_segment(seg) && (sel & 4) ) + if ( is_x86_system_segment(seg) && (sel & 4) ) goto raise_exn; switch ( rc = ops->read(sel_seg, sel & 0xfff8, &desc, sizeof(desc), ctxt) ) @@ -1424,14 +1429,11 @@ protmode_load_seg( return rc; } - if ( !is_x86_user_segment(seg) ) - { - /* System segments must have S flag == 0. */ - if ( desc.b & (1u << 12) ) - goto raise_exn; - } + /* System segments must have S flag == 0. */ + if ( is_x86_system_segment(seg) && (desc.b & (1u << 12)) ) + goto raise_exn; /* User segments must have S flag == 1. */ - else if ( !(desc.b & (1u << 12)) ) + if ( is_x86_user_segment(seg) && !(desc.b & (1u << 12)) ) goto raise_exn; dpl = (desc.b >> 13) & 3; @@ -1493,10 +1495,17 @@ protmode_load_seg( ((dpl < cpl) || (dpl < rpl)) ) goto raise_exn; break; + case x86_seg_none: + /* Non-conforming segment: check DPL against RPL and CPL. */ + if ( ((desc.b & (0x1c << 8)) != (0x1c << 8)) && + ((dpl < cpl) || (dpl < rpl)) ) + return X86EMUL_EXCEPTION; + a_flag = 0; + break; } /* Segment present in memory? */ - if ( !(desc.b & (1 << 15)) ) + if ( !(desc.b & (1 << 15)) && seg != x86_seg_none ) { fault_type = seg != x86_seg_ss ? EXC_NP : EXC_SS; goto raise_exn; @@ -1504,7 +1513,7 @@ protmode_load_seg( if ( !is_x86_user_segment(seg) ) { - int lm = in_longmode(ctxt, ops); + int lm = (desc.b & (1u << 12)) ? 0 : in_longmode(ctxt, ops); if ( lm < 0 ) return X86EMUL_UNHANDLEABLE; @@ -1524,7 +1533,8 @@ protmode_load_seg( return rc; } if ( (desc_hi.b & 0x00001f00) || - !is_canonical_address((uint64_t)desc_hi.a << 32) ) + (seg != x86_seg_none && + !is_canonical_address((uint64_t)desc_hi.a << 32)) ) goto raise_exn; } } @@ -1567,7 +1577,8 @@ protmode_load_seg( return X86EMUL_OKAY; raise_exn: - generate_exception(fault_type, sel & 0xfffc); + generate_exception_if(seg != x86_seg_none, fault_type, sel & 0xfffc); + rc = X86EMUL_EXCEPTION; done: return rc; } @@ -4421,6 +4432,29 @@ x86_emulate( if ( (rc = load_seg(seg, src.val, 0, NULL, ctxt, ops)) != 0 ) goto done; break; + case 4: /* verr / verw */ + _regs.eflags &= ~EFLG_ZF; + switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, + &sreg, ctxt, ops) ) + { + case X86EMUL_OKAY: + if ( sreg.attr.fields.s && + ((modrm_reg & 1) ? ((sreg.attr.fields.type & 0xa) == 0x2) + : ((sreg.attr.fields.type & 0xa) != 0x8)) ) + _regs.eflags |= EFLG_ZF; + break; + case X86EMUL_EXCEPTION: + if ( ctxt->event_pending ) + { + ASSERT(ctxt->event.vector == EXC_PF); + default: + goto done; + } + /* Instead of the exception, ZF remains cleared. */ + rc = X86EMUL_OKAY; + break; + } + break; default: generate_exception_if(true, EXC_UD); break; @@ -4629,6 +4663,98 @@ x86_emulate( break; } + case X86EMUL_OPC(0x0f, 0x02): /* lar */ + generate_exception_if(!in_protmode(ctxt, ops), EXC_UD); + _regs.eflags &= ~EFLG_ZF; + switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg, + ctxt, ops) ) + { + case X86EMUL_OKAY: + if ( !sreg.attr.fields.s ) + { + switch ( sreg.attr.fields.type ) + { + case 0x01: /* available 16-bit TSS */ + case 0x03: /* busy 16-bit TSS */ + case 0x04: /* 16-bit call gate */ + case 0x05: /* 16/32-bit task gate */ + if ( in_longmode(ctxt, ops) ) + break; + /* fall through */ + case 0x02: /* LDT */ + case 0x09: /* available 32/64-bit TSS */ + case 0x0b: /* busy 32/64-bit TSS */ + case 0x0c: /* 32/64-bit call gate */ + _regs.eflags |= EFLG_ZF; + break; + } + } + else + _regs.eflags |= EFLG_ZF; + break; + case X86EMUL_EXCEPTION: + if ( ctxt->event_pending ) + { + ASSERT(ctxt->event.vector == EXC_PF); + default: + goto done; + } + /* Instead of the exception, ZF remains cleared. */ + rc = X86EMUL_OKAY; + break; + } + if ( _regs.eflags & EFLG_ZF ) + dst.val = ((sreg.attr.bytes & 0xff) << 8) | + ((sreg.limit >> (sreg.attr.fields.g ? 12 : 0)) & + 0xf0000) | + ((sreg.attr.bytes & 0xf00) << 12); + else + dst.type = OP_NONE; + break; + + case X86EMUL_OPC(0x0f, 0x03): /* lsl */ + generate_exception_if(!in_protmode(ctxt, ops), EXC_UD); + _regs.eflags &= ~EFLG_ZF; + switch ( rc = protmode_load_seg(x86_seg_none, src.val, false, &sreg, + ctxt, ops) ) + { + case X86EMUL_OKAY: + if ( !sreg.attr.fields.s ) + { + switch ( sreg.attr.fields.type ) + { + case 0x01: /* available 16-bit TSS */ + case 0x03: /* busy 16-bit TSS */ + if ( in_longmode(ctxt, ops) ) + break; + /* fall through */ + case 0x02: /* LDT */ + case 0x09: /* available 32/64-bit TSS */ + case 0x0b: /* busy 32/64-bit TSS */ + _regs.eflags |= EFLG_ZF; + break; + } + } + else + _regs.eflags |= EFLG_ZF; + break; + case X86EMUL_EXCEPTION: + if ( ctxt->event_pending ) + { + ASSERT(ctxt->event.vector == EXC_PF); + default: + goto done; + } + /* Instead of the exception, ZF remains cleared. */ + rc = X86EMUL_OKAY; + break; + } + if ( _regs.eflags & EFLG_ZF ) + dst.val = sreg.limit; + else + dst.type = OP_NONE; + break; + case X86EMUL_OPC(0x0f, 0x05): /* syscall */ { uint64_t msr_content;