On Tue, Oct 03, 2017 at 08:54:16PM -0700, Ricardo Neri wrote: > When computing a linear address and segmentation is used, we need to know > the base address of the segment involved in the computation. In most of > the cases, the segment base address will be zero as in USER_DS/USER32_DS.
... > --- > arch/x86/include/asm/inat.h | 10 ++ > arch/x86/lib/insn-eval.c | 321 > ++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 331 insertions(+) Ok, some more fixes ontop. I carved out the code under the resolve_default_idx: label into a separate function. This made resolve_seg_reg() pretty-much trivial to follow. Also renamed some functions and variables to better denote what they do. Please add Improvements-by: Borislav Petkov <[email protected]> to your commit message if you use this. Thanks. --- diff --git a/arch/x86/lib/insn-eval.c b/arch/x86/lib/insn-eval.c index 77b48f99d73a..d02b94ace0f1 100644 --- a/arch/x86/lib/insn-eval.c +++ b/arch/x86/lib/insn-eval.c @@ -49,7 +49,7 @@ static bool is_string_insn(struct insn *insn) } /** - * get_overridden_seg_reg_idx() - obtain segment register override index + * get_seg_reg_override_idx() - obtain segment register override index * @insn: Instruction with segment override prefixes * * Inspect the instruction prefixes and find segment overrides, if any. @@ -62,10 +62,10 @@ static bool is_string_insn(struct insn *insn) * * -EINVAL in case of error. */ -static int get_overridden_seg_reg_idx(struct insn *insn) +static int get_seg_reg_override_idx(struct insn *insn) { int idx = INAT_SEG_REG_DEFAULT; - int sel_overrides = 0, i; + int num_overrides = 0, i; if (!insn) return -EINVAL; @@ -80,41 +80,41 @@ static int get_overridden_seg_reg_idx(struct insn *insn) switch (attr) { case INAT_MAKE_PREFIX(INAT_PFX_CS): idx = INAT_SEG_REG_CS; - sel_overrides++; + num_overrides++; break; case INAT_MAKE_PREFIX(INAT_PFX_SS): idx = INAT_SEG_REG_SS; - sel_overrides++; + num_overrides++; break; case INAT_MAKE_PREFIX(INAT_PFX_DS): idx = INAT_SEG_REG_DS; - sel_overrides++; + num_overrides++; break; case INAT_MAKE_PREFIX(INAT_PFX_ES): idx = INAT_SEG_REG_ES; - sel_overrides++; + num_overrides++; break; case INAT_MAKE_PREFIX(INAT_PFX_FS): idx = INAT_SEG_REG_FS; - sel_overrides++; + num_overrides++; break; case INAT_MAKE_PREFIX(INAT_PFX_GS): idx = INAT_SEG_REG_GS; - sel_overrides++; + num_overrides++; break; /* No default action needed. */ } } /* More than one segment override prefix leads to undefined behavior. */ - if (sel_overrides > 1) + if (num_overrides > 1) return -EINVAL; return idx; } /** - * allow_seg_reg_overrides() - check if segment override prefixes are allowed + * check_seg_overrides() - check if segment override prefixes are allowed * @insn: Instruction with segment override prefixes * @regoff: Operand offset, in pt_regs, for which the check is performed * @@ -129,7 +129,7 @@ static int get_overridden_seg_reg_idx(struct insn *insn) * * -EINVAL in case of error. */ -static int allow_seg_reg_overrides(struct insn *insn, int regoff) +static int check_seg_overrides(struct insn *insn, int regoff) { /* * Segment override prefixes should not be used for rIP. It is not @@ -148,6 +148,55 @@ static int allow_seg_reg_overrides(struct insn *insn, int regoff) return 1; } +static int resolve_default_seg(struct insn *insn, struct pt_regs *regs, int off) +{ + if (user_64bit_mode(regs)) + return INAT_SEG_REG_IGNORE; + + /* + * If we are here, we use the default segment register as described + * in the Intel documentation: + * + * + DS for all references involving r[ABCD]X, and rSI. + * + If used in a string instruction, ES for rDI. Otherwise, DS. + * + AX, CX and DX are not valid register operands in 16-bit addresses. + * encodings but are valid for 32-bit and 64-bit encodings. + * + -EDOM is reserved to identify for cases in which no register + * is used (i.e., displacement-only addressing). Use DS. + * + SS for (E)SP or (E)BP. + * + CS for (E)IP. + */ + switch (off) { + case offsetof(struct pt_regs, ax): + case offsetof(struct pt_regs, cx): + case offsetof(struct pt_regs, dx): + /* Need insn to verify address size. */ + if (insn->addr_bytes == 2) + return -EINVAL; + + case -EDOM: + case offsetof(struct pt_regs, bx): + case offsetof(struct pt_regs, si): + return INAT_SEG_REG_DS; + + case offsetof(struct pt_regs, di): + if (is_string_insn(insn)) + return INAT_SEG_REG_ES; + return INAT_SEG_REG_DS; + + case offsetof(struct pt_regs, bp): + case offsetof(struct pt_regs, sp): + return INAT_SEG_REG_SS; + + case offsetof(struct pt_regs, ip): + return INAT_SEG_REG_CS; + + default: + return -EINVAL; + } +} + + /** * resolve_seg_reg() - obtain segment register index * @insn: Instruction with operands @@ -194,24 +243,24 @@ static int allow_seg_reg_overrides(struct insn *insn, int regoff) */ static int resolve_seg_reg(struct insn *insn, struct pt_regs *regs, int regoff) { - int use_pfx_overrides, idx; + int ret, idx; - use_pfx_overrides = allow_seg_reg_overrides(insn, regoff); - if (use_pfx_overrides < 0) - return use_pfx_overrides; + ret = check_seg_overrides(insn, regoff); + if (ret < 0) + return ret; - if (use_pfx_overrides == 0) - goto resolve_default_idx; + if (!ret) + return resolve_default_seg(insn, regs, regoff); if (!insn) return -EINVAL; - idx = get_overridden_seg_reg_idx(insn); + idx = get_seg_reg_override_idx(insn); if (idx < 0) return idx; if (idx == INAT_SEG_REG_DEFAULT) - goto resolve_default_idx; + return resolve_default_seg(insn, regs, regoff); /* * In long mode, segment override prefixes are ignored, except for @@ -224,53 +273,6 @@ static int resolve_seg_reg(struct insn *insn, struct pt_regs *regs, int regoff) } return idx; - -resolve_default_idx: - - if (user_64bit_mode(regs)) - return INAT_SEG_REG_IGNORE; - /* - * If we are here, we use the default segment register as described - * in the Intel documentation: - * - * + DS for all references involving r[ABCD]X, and rSI. - * + If used in a string instruction, ES for rDI. Otherwise, DS. - * + AX, CX and DX are not valid register operands in 16-bit addresses. - * encodings but are valid for 32-bit and 64-bit encodings. - * + -EDOM is reserved to identify for cases in which no register - * is used (i.e., displacement-only addressing). Use DS. - * + SS for (E)SP or (E)BP. - * + CS for (E)IP. - */ - - switch (regoff) { - case offsetof(struct pt_regs, ax): - case offsetof(struct pt_regs, cx): - case offsetof(struct pt_regs, dx): - /* Need insn to verify address size. */ - if (insn->addr_bytes == 2) - return -EINVAL; - - case -EDOM: - case offsetof(struct pt_regs, bx): - case offsetof(struct pt_regs, si): - return INAT_SEG_REG_DS; - - case offsetof(struct pt_regs, di): - if (is_string_insn(insn)) - return INAT_SEG_REG_ES; - return INAT_SEG_REG_DS; - - case offsetof(struct pt_regs, bp): - case offsetof(struct pt_regs, sp): - return INAT_SEG_REG_SS; - - case offsetof(struct pt_regs, ip): - return INAT_SEG_REG_CS; - - default: - return -EINVAL; - } } /** -- Regards/Gruss, Boris. SUSE Linux GmbH, GF: Felix Imendörffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nürnberg) --

