Re: [PATCH v8 12/28] x86/insn-eval: Add utility functions to get segment selector
From: Ricardo Neri
Date: Wed Sep 27 2017 - 00:22:53 EST
On Tue, 2017-09-26 at 12:43 +0200, Borislav Petkov wrote:
> Hi,
>
> On Fri, Aug 18, 2017 at 05:27:53PM -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ÂÂÂÂ| 278
> > ++++++++++++++++++++++++++++++++++++++++++++
> > Â2 files changed, 288 insertions(+)
> so I did a bunch of simplifications on top, see if you agree:
>
> * we should always test for if (!insn) first because otherwise we
> can't talk
> about a segment at all.
This is true except when we don't have an insn at all (well, it may be
non-NULL but it will only contain garbage). The case to which I am
referring is when we begin decoding our instruction. The first step is
to copy_from_user the instruction and populate insn. For this we must
calculate the linear address from where we copy using CS and rIP.
Furthermore, in this only case we don't need to look at insn at all as
the only register involved is rIP no segment override prefixes are
allowed.
Please see my comment below.
>
> * the nomenclature should be clear: if we return INAT_SEG_REG_* those
> are own
> defined indices and not registers or prefixes or whatever else, so
> everywhere we
> state that we're returning an *index*.
I agree.
>
> * and then shorten local variables' names as reading "reg" every
> other line doesn't make it clearer :)
I agree.
>
> * also some comments formatting for better readability.
Thanks!
>
> * and prefixing register names with "r" in the comments means then
> all
> register widths, not only 32-bit. Dunno, is "(E)" SDM nomenclature
> for
> the different register widths?
A quick look at the section 3.1.1.3 of the Intel Software Development
Manual Vol2 reveals that r/m16 operands are referred as [ACDB]X,
[SB]P,and [SD]I. r/m32 operands are referred as E[ACDB]X, E[SB]P and
E[SD]I. r/m64 operands are referred as R[ACDB]X, R[SB]P, R[SD]I and
R[8-15].
Also, some instructions (e.g., string structions) do use the
nomenclature (E)[DI]I protected mode and (R|E)[DI]I for long mode.
I only used "(E)" (i.e., not the "(R|)" part) as these utility
functions will deal mostly with protected mode, unless FS or GS are
used in long mode.
>
> ---
> diff --git a/arch/x86/lib/insn-eval.c b/arch/x86/lib/insn-eval.c
> index 86f58ce6c302..720529573d72 100644
> --- a/arch/x86/lib/insn-eval.c
> +++ b/arch/x86/lib/insn-eval.c
> @@ -44,50 +44,45 @@ static bool is_string_insn(struct insn *insn)
> Â}
> Â
> Â/**
> - * get_overridden_seg_reg() - obtain segment register to use from
> prefixes
> - * @insn: Instruction structure with segment override
> prefixes
> - * @regs: Structure with register values as seen when
> entering kernel mode
> + * get_seg_reg_idx() - obtain segment register index to use from
> prefixes
> + * @insn: Instruction with segment override prefixes
> + * @regs: Register values as seen when entering kernel mode
> Â * @regoff: Operand offset, in pt_regs, used to deterimine
> segment register
> Â *
> - * The segment register to which an effective address refers depends
> on
> - * a) whether running in long mode (in such a case semgment override
> prefixes
> - * are ignored. b) Whether segment override prefixes must be ignored
> for certain
> - * registers: always use CS when the register is (R|E)IP; always use
> ES when
> - * operand register is (E)DI with a string instruction as defined in
> the Intel
> - * documentation. c) If segment overrides prefixes are found in the
> instruction
> - * prefixes. d) Use the default segment register associated with the
> operand
> - * register.
> + * The segment register to which an effective address refers,
> depends on:
> + *
> + * a) whether running in long mode (in such a case segment override
> prefixes
> + * are ignored).
> + *
> + * b) Whether segment override prefixes must be ignored for certain
> + * registers: always use CS when the register is rIP; always use ES
> when
> + * operand register is rDI with a string instruction as defined in
> the Intel
> + * documentation.
> Â *
> - * This function returns the overridden segment register to use, if
> any, as per
> - * the conditions described above. Please note that this function
> + * c) If segment overrides prefixes are found in the instruction
> prefixes.
> + *
> + * d) Use the default segment register associated with the operand
> register.
> + *
> + * This function returns the segment register override to use, if
> any,
> + * as per the conditions described above. Please note that this
> function
> Â * does not return the value in the segment register (i.e., the
> segment
> - * selector). The segment selector needs to be obtained using
> - * get_segment_selector() and passing the segment register resolved
> by
> + * selector) but our defined index. The segment selector needs to be
> obtained
> + * using get_segment_selector() and passing the segment register
> resolved by
> Â * this function.
> Â *
> - * Return: A constant identifying the segment register to use, among
> CS, SS, DS,
> + * Returns:
> + *
> + * A constant identifying the segment register to use, among CS, SS,
> DS,
> Â * ES, FS, or GS. INAT_SEG_REG_IGNORE is returned if running in long
> mode.
> Â * INAT_SEG_REG_DEFAULT is returned if no segment override prefixes
> were found
> - * and the default segment register shall be used. -EINVAL in case
> of error.
> + * and the default segment register shall be used.
> + *
> + * -EINVAL in case of error.
> Â */
This rewording looks OK to me. Thanks!
> -static int get_overridden_seg_reg(struct insn *insn, struct pt_regs
> *regs,
> - ÂÂint regoff)
> +static int get_seg_reg_idx(struct insn *insn, struct pt_regs *regs,
> int regoff)
> Â{
> - int i;
> - int sel_overrides = 0;
> - int seg_register = INAT_SEG_REG_DEFAULT;
> -
> - /*
> - Â* Segment override prefixes should not be used for (E)IP.
> Check this
> - Â* case first as we might not have (and not needed at all) a
> - Â* valid insn structure to evaluate segment override
> prefixes.
> - Â*/
> - if (regoff == offsetof(struct pt_regs, ip)) {
> - if (user_64bit_mode(regs))
> - return INAT_SEG_REG_IGNORE;
> - else
> - return INAT_SEG_REG_DEFAULT;
> - }
This function essentially inspects insn to find segment override
prefixes. However, if called with rIP, we still don't have any
instruction to inspect (we are yet to copy_from_user it). insn would
essentially contain garbage. I guess callers could zero-init insn in
such a case. However, I think that keeping this check makes things more
clear.
> + int idx = INAT_SEG_REG_DEFAULT;
> + int sel_overrides = 0, i;
> Â
> Â if (!insn)
> Â return -EINVAL;
> @@ -101,27 +96,27 @@ static int get_overridden_seg_reg(struct insn
> *insn, struct pt_regs *regs,
> Â attr = inat_get_opcode_attribute(insn-
> >prefixes.bytes[i]);
> Â switch (attr) {
> Â case INAT_MAKE_PREFIX(INAT_PFX_CS):
> - seg_register = INAT_SEG_REG_CS;
> + idx = INAT_SEG_REG_CS;
> Â sel_overrides++;
> Â break;
> Â case INAT_MAKE_PREFIX(INAT_PFX_SS):
> - seg_register = INAT_SEG_REG_SS;
> + idx = INAT_SEG_REG_SS;
> Â sel_overrides++;
> Â break;
> Â case INAT_MAKE_PREFIX(INAT_PFX_DS):
> - seg_register = INAT_SEG_REG_DS;
> + idx = INAT_SEG_REG_DS;
> Â sel_overrides++;
> Â break;
> Â case INAT_MAKE_PREFIX(INAT_PFX_ES):
> - seg_register = INAT_SEG_REG_ES;
> + idx = INAT_SEG_REG_ES;
> Â sel_overrides++;
> Â break;
> Â case INAT_MAKE_PREFIX(INAT_PFX_FS):
> - seg_register = INAT_SEG_REG_FS;
> + idx = INAT_SEG_REG_FS;
> Â sel_overrides++;
> Â break;
> Â case INAT_MAKE_PREFIX(INAT_PFX_GS):
> - seg_register = INAT_SEG_REG_GS;
> + idx = INAT_SEG_REG_GS;
> Â sel_overrides++;
> Â break;
> Â /* No default action needed. */
> @@ -133,26 +128,26 @@ static int get_overridden_seg_reg(struct insn
> *insn, struct pt_regs *regs,
> Â Â* overrides for FS and GS.
> Â Â*/
> Â if (user_64bit_mode(regs)) {
> - if (seg_register != INAT_SEG_REG_FS &&
> - ÂÂÂÂseg_register != INAT_SEG_REG_GS)
> + if (idx != INAT_SEG_REG_FS &&
> + ÂÂÂÂidx != INAT_SEG_REG_GS)
> Â return INAT_SEG_REG_IGNORE;
> Â /* More than one segment override prefix leads to undefined
> behavior. */
> Â } else if (sel_overrides > 1) {
> Â return -EINVAL;
> Â /*
> Â Â* Segment override prefixes are always ignored for string
> instructions
> - Â* that involve the use the (E)DI register.
> + Â* that use the (E)DI register.
> Â Â*/
> Â } else if ((regoff == offsetof(struct pt_regs, di)) &&
> Â ÂÂÂis_string_insn(insn)) {
> Â return INAT_SEG_REG_DEFAULT;
> Â }
> Â
> - return seg_register;
I will change to use indexes as you suggested.
> + return idx;
> Â}
> Â
> Â/**
> - * resolve_seg_register() - obtain segment register
> + * resolve_seg_reg() - obtain segment register index
> Â * @insn: Instruction structure with segment override
> prefixes
> Â * @regs: Structure with register values as seen when
> entering kernel mode
> Â * @regoff: Operand offset, in pt_regs, used to deterimine
> segment register
> @@ -169,36 +164,38 @@ static int get_overridden_seg_reg(struct insn
> *insn, struct pt_regs *regs,
> Â *
> Â * Return: A constant identifying the segment register to use, among
> CS, SS, DS,
> Â * ES, FS, or GS. INAT_SEG_REG_IGNORE is returned if running in long
> mode.
> + *
> Â * -EINVAL in case of error.
> Â */
> -static int resolve_seg_register(struct insn *insn, struct pt_regs
> *regs,
> - int regoff)
> +static int resolve_seg_reg(struct insn *insn, struct pt_regs *regs,
> int regoff)
> Â{
> - int seg_reg;
> + int idx;
> Â
> - seg_reg = get_overridden_seg_reg(insn, regs, regoff);
> + if (!insn)
> + return -EINVAL;
I checked for a NULL insn only after get_overriden_seg_reg (now
get_seg_reg_idx) because such function is able to handle a null insn.
However, this function not always needs a non-NULL insn. If obtaing the
regment register for rIP, there is not need to inspect the instruction
at all.
I only check for a NULL insn when needed (i.e., the contents of the
instruction could change the used segment register).
> Â
> - if (seg_reg < 0)
> - return seg_reg;
> + idx = get_seg_reg_idx(insn, regs, regoff);
> + if (idx < 0)
> + return idx;
> Â
> - if (seg_reg == INAT_SEG_REG_IGNORE)
> - return seg_reg;
> + if (idx == INAT_SEG_REG_IGNORE)
> + return idx;
> Â
> - if (seg_reg != INAT_SEG_REG_DEFAULT)
> - return seg_reg;
> + if (idx != INAT_SEG_REG_DEFAULT)
> + return idx;
> Â
> Â /*
> Â Â* If we are here, we use the default segment register as
> described
> Â Â* in the Intel documentation:
> - Â*ÂÂ+ DS for all references involving (E)AX, (E)CX, (E)DX,
> (E)BX, and
> - Â* (E)SI.
> - Â*ÂÂ+ If used in a string instruction, ES for (E)DI.
> Otherwise, DS.
> + Â*
> + Â*ÂÂ+ 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 address
> Â Â*ÂÂÂÂ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.
> + Â*ÂÂ+ SS for rSP or rBP.
> + Â*ÂÂ+ CS for rIP.
> Â Â*/
Thanks for the rewording!
> Â
> Â switch (regoff) {
> @@ -206,24 +203,26 @@ static int resolve_seg_register(struct insn
> *insn, struct pt_regs *regs,
> Â case offsetof(struct pt_regs, cx):
> Â case offsetof(struct pt_regs, dx):
> Â /* Need insn to verify address size. */
> - if (!insn || insn->addr_bytes == 2)
> + if (insn->addr_bytes == 2)
Here we care if insn is NULL as we need to look at the address size.
> Â 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):
> - /* Need insn to see if insn is string instruction.
> */
> - if (!insn)
> - return -EINVAL;
Here we need a valid insn to determine if it contains a string
instruction.
> Â 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;
For CS we don't need insn at all.
> +
> Â default:
> Â return -EINVAL;
> Â }
> @@ -232,17 +231,20 @@ static int resolve_seg_register(struct insn
> *insn, struct pt_regs *regs,
> Â/**
> Â * get_segment_selector() - obtain segment selector
> Â * @regs: Structure with register values as seen when
> entering kernel mode
> - * @seg_reg: Segment register to use
> + * @seg_reg: Segment register index to use
> Â *
> - * Obtain the segment selector from any of the CS, SS, DS, ES, FS,
> GS segment
> - * registers. In CONFIG_X86_32, the segment is obtained from either
> pt_regs or
> - * kernel_vm86_regs as applicable. In CONFIG_X86_64, CS and SS are
> obtained
> + * Obtain the segment selector from any of the CS, SS, DS, ES, FS,
> GS
> + * segment registers. In CONFIG_X86_32, the segment is obtained from
> either
> + * pt_regs or kernel_vm86_regs as applicable. On 64-bit, CS and SS
> are obtained
> Â * from pt_regs. DS, ES, FS and GS are obtained by reading the
> actual CPU
> - * registers. This done for only for completeness as in
> CONFIG_X86_64 segment
> - * registers are ignored.
> + * registers. This done only for completeness as in long mode
> segment registers
> + * are ignored.
> + *
> + * Returns:
> + *
> + * Value of the segment selector, including null when running in
> long mode.
> Â *
> - * Return: Value of the segment selector, including null when
> running in
> - * long mode. -1 on error.
> + * -EINVAL on error.
Thanks for the rewording. I will incorporate it in the series.
Thanks and BR,
Ricardo