Tasks running in virtual-8086 mode will use 16-bit addressing form encodings as described in the Intel 64 and IA-32 Architecture Software Developer's Manual Volume 2A Section 2.1.5. 16-bit addressing encodings differ in several ways from the 32-bit/64-bit addressing form encodings: the r/m part of the ModRM byte points to different registers and, in some cases, addresses can be indicated by the addition of the value of two registers. Also, there is no support for SiB bytes. Thus, a separate function is needed to parse this form of addressing.
Furthermore, virtual-8086 mode tasks will use real-mode addressing. This implies that the segment selectors do not point to a segment descriptor but are used to compute logical addresses. Hence, there is a need to add support to compute addresses using the segment selectors. If segment- override prefixes are present in the instructions, they take precedence.
Lastly, it is important to note that when a tasks is running in virtual- 8086 mode and an interrupt/exception occurs, the CPU pushes to the stack the segment selectors for ds, es, fs and gs. These are accesible via the struct kernel_vm86_regs rather than pt_regs.
Code for 16-bit addressing encodings is likely to be used only by virtual- 8086 mode tasks. Thus, this code is wrapped to be built only if the option CONFIG_VM86 is selected.
Cc: Dave Hansen dave.hansen@linux.intel.com Cc: Adam Buchbinder adam.buchbinder@gmail.com Cc: Colin Ian King colin.king@canonical.com Cc: Lorenzo Stoakes lstoakes@gmail.com Cc: Qiaowei Ren qiaowei.ren@intel.com Cc: Arnaldo Carvalho de Melo acme@redhat.com Cc: Masami Hiramatsu mhiramat@kernel.org Cc: Adrian Hunter adrian.hunter@intel.com Cc: Kees Cook keescook@chromium.org Cc: Thomas Garnier thgarnie@google.com Cc: Peter Zijlstra peterz@infradead.org Cc: Borislav Petkov bp@suse.de Cc: Dmitry Vyukov dvyukov@google.com Cc: Ravi V. Shankar ravi.v.shankar@intel.com Cc: x86@kernel.org Signed-off-by: Ricardo Neri ricardo.neri-calderon@linux.intel.com --- arch/x86/lib/insn-kernel.c | 192 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+)
diff --git a/arch/x86/lib/insn-kernel.c b/arch/x86/lib/insn-kernel.c index 267cab4..10afdf3 100644 --- a/arch/x86/lib/insn-kernel.c +++ b/arch/x86/lib/insn-kernel.c @@ -8,6 +8,7 @@ #include <asm/inat.h> #include <asm/insn.h> #include <asm/insn-kernel.h> +#include <asm/vm86.h>
enum reg_type { REG_TYPE_RM = 0, @@ -95,6 +96,194 @@ static int get_reg_offset(struct insn *insn, struct pt_regs *regs, return regoff[regno]; }
+#ifdef CONFIG_VM86 + +/* + * Obtain the segment selector to use based on any prefixes in the instruction + * or in the offset of the register given by the r/m part of the ModRM byte. The + * register offset is as found in struct pt_regs. + */ +static unsigned short __get_segment_selector_16(struct pt_regs *regs, + struct insn *insn, int regoff) +{ + int i; + + struct kernel_vm86_regs *vm86regs = (struct kernel_vm86_regs *)regs; + + /* + * If not in virtual-8086 mode, the segment selector is not used + * to compute addresses but to select the segment descriptor. Return + * 0 to ease the computation of address. + */ + if (!v8086_mode(regs)) + return 0; + + insn_get_prefixes(insn); + + /* Check first if we have selector overrides */ + for (i = 0; i < insn->prefixes.nbytes; i++) { + switch (insn->prefixes.bytes[i]) { + /* + * Code and stack segment selector register are saved in all + * processor modes. Thus, it makes sense to take them + * from pt_regs. + */ + case 0x2e: + return (unsigned short)regs->cs; + case 0x36: + return (unsigned short)regs->ss; + /* + * The rest of the segment selector registers are only saved + * in virtual-8086 mode. Thus, we must obtain them from the + * vm86 register structure. + */ + case 0x3e: + return vm86regs->ds; + case 0x26: + return vm86regs->es; + case 0x64: + return vm86regs->fs; + case 0x65: + return vm86regs->gs; + /* + * No default action needed. We simply did not find any + * relevant prefixes. + */ + } + } + + /* + * If no overrides, use default selectors as described in the + * Intel documentationn. + */ + switch (regoff) { + case -EINVAL: /* no register involved in address computation */ + case offsetof(struct pt_regs, bx): + case offsetof(struct pt_regs, di): + case offsetof(struct pt_regs, si): + return vm86regs->ds; + case offsetof(struct pt_regs, bp): + case offsetof(struct pt_regs, sp): + return (unsigned short)regs->ss; + /* ax, cx, dx are not valid registers for 16-bit addressing*/ + default: + return -EINVAL; + } +} + +/* + * Obtain offsets from pt_regs to the two registers indicated by the + * r/m part of the ModRM byte. A negative offset indicates that the + * register should not be used. + */ +static int get_reg_offset_16(struct insn *insn, struct pt_regs *regs, + int *offs1, int *offs2) +{ + /* 16-bit addressing can use one or two registers */ + static const int regoff1[] = { + offsetof(struct pt_regs, bx), + offsetof(struct pt_regs, bx), + offsetof(struct pt_regs, bp), + offsetof(struct pt_regs, bp), + offsetof(struct pt_regs, si), + offsetof(struct pt_regs, di), + offsetof(struct pt_regs, bp), + offsetof(struct pt_regs, bx), + }; + + static const int regoff2[] = { + offsetof(struct pt_regs, si), + offsetof(struct pt_regs, di), + offsetof(struct pt_regs, si), + offsetof(struct pt_regs, di), + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + }; + + if (!offs1 || !offs2) + return -EINVAL; + + /* operand is a register, use the generic function */ + if (X86_MODRM_MOD(insn->modrm.value) == 3) { + *offs1 = insn_get_reg_offset_rm(insn, regs); + *offs2 = -EINVAL; + return 0; + } + + *offs1 = regoff1[X86_MODRM_RM(insn->modrm.value)]; + *offs2 = regoff2[X86_MODRM_RM(insn->modrm.value)]; + + /* + * If no displacement is indicated in the mod part of the ModRM byte, + * (mod part is 0) and the r/m part of the same byte is 6, no register + * is used caculate the operand address. An r/m part of 6 means that + * the second register offset is already invalid. + */ + if ((X86_MODRM_MOD(insn->modrm.value) == 0) && + (X86_MODRM_RM(insn->modrm.value) == 6)) + *offs1 = -EINVAL; + + return 0; +} + +static void __user *insn_get_addr_ref_16(struct insn *insn, + struct pt_regs *regs) +{ + unsigned long addr; + unsigned short addr1 = 0, addr2 = 0; + int addr_offset1, addr_offset2; + int ret; + unsigned short seg = 0; + + insn_get_displacement(insn); + + /* + * If operand is a register, the layout is the same as in + * 32-bit and 64-bit addressing. + */ + if (X86_MODRM_MOD(insn->modrm.value) == 3) { + addr_offset1 = get_reg_offset(insn, regs, REG_TYPE_RM); + if (addr_offset1 < 0) + goto out_err; + seg = __get_segment_selector_16(regs, insn, addr_offset1); + addr = (seg << 4) + regs_get_register(regs, addr_offset1); + } else { + ret = get_reg_offset_16(insn, regs, &addr_offset1, + &addr_offset2); + if (ret < 0) + goto out_err; + /* + * Don't fail on invalid offset values. They might be invalid + * because they are not supported. Instead, use them in the + * calculation only if they contain a valid value. + */ + if (addr_offset1 >= 0) + addr1 = regs_get_register(regs, addr_offset1); + if (addr_offset2 >= 0) + addr2 = regs_get_register(regs, addr_offset2); + seg = __get_segment_selector_16(regs, insn, addr_offset1); + if (seg < 0) + goto out_err; + addr = (seg << 4) + addr1 + addr2; + } + addr += insn->displacement.value; + + return (void __user *)addr; +out_err: + return (void __user *)-1; +} +#else + +static void __user *insn_get_addr_ref_16(struct insn *insn, + struct pt_regs *regs) +{ + return (void __user *)-1; +} + +#endif /* CONFIG_VM86 */ + int insn_get_reg_offset_rm(struct insn *insn, struct pt_regs *regs) { return get_reg_offset(insn, regs, REG_TYPE_RM); @@ -111,6 +300,9 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs) int addr_offset, base_offset, indx_offset; insn_byte_t sib;
+ if (insn->addr_bytes == 2) + return insn_get_addr_ref_16(insn, regs); + insn_get_modrm(insn); insn_get_sib(insn); sib = insn->sib.value;