Commit 70e57c0f authored by Ricardo Neri's avatar Ricardo Neri Committed by Ingo Molnar

x86/insn-eval: Compute linear address in several utility functions

Computing a linear address involves several steps. The first step is to
compute the effective address. This requires determining the addressing
mode in use and perform arithmetic operations on the operands. Plus, each
addressing mode has special cases that must be handled.

Once the effective address is known, the base address of the applicable
segment is added to obtain the linear address.

Clearly, this is too much work for a single function. Instead, handle each
addressing mode in a separate utility function. This improves readability
and gives us the opportunity to handler errors better.

At the moment, arithmetic to compute the effective address uses 64-byte
variables. Thus, limit support to 64-bit addresses.

While reworking the function insn_get_addr_ref(), the variable addr_offset
is renamed as regoff to reflect its actual use (i.e., offset, from the
base of pt_regs, of the register used as operand).
Suggested-by: default avatarBorislav Petkov <bp@suse.de>
Signed-off-by: default avatarRicardo Neri <ricardo.neri-calderon@linux.intel.com>
Reviewed-by: default avatarThomas Gleixner <tglx@linutronix.de>
Cc: Adam Buchbinder <adam.buchbinder@gmail.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Chen Yucong <slaoub@gmail.com>
Cc: Chris Metcalf <cmetcalf@mellanox.com>
Cc: Colin Ian King <colin.king@canonical.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Huang Rui <ray.huang@amd.com>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Lorenzo Stoakes <lstoakes@gmail.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Michael S. Tsirkin <mst@redhat.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Paul Gortmaker <paul.gortmaker@windriver.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Qiaowei Ren <qiaowei.ren@intel.com>
Cc: Ravi V. Shankar <ravi.v.shankar@intel.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Thomas Garnier <thgarnie@google.com>
Cc: Tony Luck <tony.luck@intel.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: ricardo.neri@intel.com
Link: http://lkml.kernel.org/r/1509935277-22138-2-git-send-email-ricardo.neri-calderon@linux.intel.comSigned-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 93c08089
...@@ -776,85 +776,212 @@ static int get_seg_base_limit(struct insn *insn, struct pt_regs *regs, ...@@ -776,85 +776,212 @@ static int get_seg_base_limit(struct insn *insn, struct pt_regs *regs,
return 0; return 0;
} }
/* /**
* return the address being referenced be instruction * get_eff_addr_reg() - Obtain effective address from register operand
* for rm=3 returning the content of the rm reg * @insn: Instruction. Must be valid.
* for rm!=3 calculates the address using SIB and Disp * @regs: Register values as seen when entering kernel mode
* @regoff: Obtained operand offset, in pt_regs, with the effective address
* @eff_addr: Obtained effective address
*
* Obtain the effective address stored in the register operand as indicated by
* the ModRM byte. This function is to be used only with register addressing
* (i.e., ModRM.mod is 3). The effective address is saved in @eff_addr. The
* register operand, as an offset from the base of pt_regs, is saved in @regoff;
* such offset can then be used to resolve the segment associated with the
* operand. This function can be used with any of the supported address sizes
* in x86.
*
* Returns:
*
* 0 on success. @eff_addr will have the effective address stored in the
* operand indicated by ModRM. @regoff will have such operand as an offset from
* the base of pt_regs.
*
* -EINVAL on error.
*/ */
void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs) static int get_eff_addr_reg(struct insn *insn, struct pt_regs *regs,
int *regoff, long *eff_addr)
{ {
int addr_offset, base_offset, indx_offset, ret; insn_get_modrm(insn);
unsigned long linear_addr = -1L, seg_base;
long eff_addr, base, indx; if (!insn->modrm.nbytes)
insn_byte_t sib; return -EINVAL;
if (X86_MODRM_MOD(insn->modrm.value) != 3)
return -EINVAL;
*regoff = get_reg_offset(insn, regs, REG_TYPE_RM);
if (*regoff < 0)
return -EINVAL;
*eff_addr = regs_get_register(regs, *regoff);
return 0;
}
/**
* get_eff_addr_modrm() - Obtain referenced effective address via ModRM
* @insn: Instruction. Must be valid.
* @regs: Register values as seen when entering kernel mode
* @regoff: Obtained operand offset, in pt_regs, associated with segment
* @eff_addr: Obtained effective address
*
* Obtain the effective address referenced by the ModRM byte of @insn. After
* identifying the registers involved in the register-indirect memory reference,
* its value is obtained from the operands in @regs. The computed address is
* stored @eff_addr. Also, the register operand that indicates the associated
* segment is stored in @regoff, this parameter can later be used to determine
* such segment.
*
* Returns:
*
* 0 on success. @eff_addr will have the referenced effective address. @regoff
* will have a register, as an offset from the base of pt_regs, that can be used
* to resolve the associated segment.
*
* -EINVAL on error.
*/
static int get_eff_addr_modrm(struct insn *insn, struct pt_regs *regs,
int *regoff, long *eff_addr)
{
long tmp;
if (insn->addr_bytes != 8)
return -EINVAL;
insn_get_modrm(insn); insn_get_modrm(insn);
insn_get_sib(insn);
sib = insn->sib.value;
if (X86_MODRM_MOD(insn->modrm.value) == 3) { if (!insn->modrm.nbytes)
addr_offset = get_reg_offset(insn, regs, REG_TYPE_RM); return -EINVAL;
if (addr_offset < 0)
goto out;
eff_addr = regs_get_register(regs, addr_offset); if (X86_MODRM_MOD(insn->modrm.value) > 2)
return -EINVAL;
*regoff = get_reg_offset(insn, regs, REG_TYPE_RM);
} else {
if (insn->sib.nbytes) {
/* /*
* Negative values in the base and index offset means * -EDOM means that we must ignore the address_offset. In such a case,
* an error when decoding the SIB byte. Except -EDOM, * in 64-bit mode the effective address relative to the rIP of the
* which means that the registers should not be used * following instruction.
* in the address computation.
*/ */
base_offset = get_reg_offset(insn, regs, REG_TYPE_BASE); if (*regoff == -EDOM) {
if (base_offset == -EDOM) if (user_64bit_mode(regs))
base = 0; tmp = regs->ip + insn->length;
else if (base_offset < 0)
goto out;
else else
base = regs_get_register(regs, base_offset); tmp = 0;
} else if (*regoff < 0) {
return -EINVAL;
} else {
tmp = regs_get_register(regs, *regoff);
}
*eff_addr = tmp + insn->displacement.value;
return 0;
}
/**
* get_eff_addr_sib() - Obtain referenced effective address via SIB
* @insn: Instruction. Must be valid.
* @regs: Register values as seen when entering kernel mode
* @regoff: Obtained operand offset, in pt_regs, associated with segment
* @eff_addr: Obtained effective address
*
* Obtain the effective address referenced by the SIB byte of @insn. After
* identifying the registers involved in the indexed, register-indirect memory
* reference, its value is obtained from the operands in @regs. The computed
* address is stored @eff_addr. Also, the register operand that indicates the
* associated segment is stored in @regoff, this parameter can later be used to
* determine such segment.
*
* Returns:
*
* 0 on success. @eff_addr will have the referenced effective address.
* @base_offset will have a register, as an offset from the base of pt_regs,
* that can be used to resolve the associated segment.
*
* -EINVAL on error.
*/
static int get_eff_addr_sib(struct insn *insn, struct pt_regs *regs,
int *base_offset, long *eff_addr)
{
long base, indx;
int indx_offset;
if (insn->addr_bytes != 8)
return -EINVAL;
insn_get_modrm(insn);
if (!insn->modrm.nbytes)
return -EINVAL;
if (X86_MODRM_MOD(insn->modrm.value) > 2)
return -EINVAL;
insn_get_sib(insn);
if (!insn->sib.nbytes)
return -EINVAL;
*base_offset = get_reg_offset(insn, regs, REG_TYPE_BASE);
indx_offset = get_reg_offset(insn, regs, REG_TYPE_INDEX); indx_offset = get_reg_offset(insn, regs, REG_TYPE_INDEX);
/*
* Negative values in the base and index offset means an error when
* decoding the SIB byte. Except -EDOM, which means that the registers
* should not be used in the address computation.
*/
if (*base_offset == -EDOM)
base = 0;
else if (*base_offset < 0)
return -EINVAL;
else
base = regs_get_register(regs, *base_offset);
if (indx_offset == -EDOM) if (indx_offset == -EDOM)
indx = 0; indx = 0;
else if (indx_offset < 0) else if (indx_offset < 0)
goto out; return -EINVAL;
else else
indx = regs_get_register(regs, indx_offset); indx = regs_get_register(regs, indx_offset);
eff_addr = base + indx * (1 << X86_SIB_SCALE(sib)); *eff_addr = base + indx * (1 << X86_SIB_SCALE(insn->sib.value));
/* *eff_addr += insn->displacement.value;
* The base determines the segment used to compute
* the linear address. return 0;
}
/*
* return the address being referenced be instruction
* for rm=3 returning the content of the rm reg
* for rm!=3 calculates the address using SIB and Disp
*/ */
addr_offset = base_offset; void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
{
unsigned long linear_addr = -1L, seg_base;
int regoff, ret;
long eff_addr;
if (X86_MODRM_MOD(insn->modrm.value) == 3) {
ret = get_eff_addr_reg(insn, regs, &regoff, &eff_addr);
if (ret)
goto out;
} else { } else {
addr_offset = get_reg_offset(insn, regs, REG_TYPE_RM); if (insn->sib.nbytes) {
/* ret = get_eff_addr_sib(insn, regs, &regoff, &eff_addr);
* -EDOM means that we must ignore the address_offset. if (ret)
* In such a case, in 64-bit mode the effective address
* relative to the RIP of the following instruction.
*/
if (addr_offset == -EDOM) {
if (user_64bit_mode(regs))
eff_addr = (long)regs->ip + insn->length;
else
eff_addr = 0;
} else if (addr_offset < 0) {
goto out; goto out;
} else { } else {
eff_addr = regs_get_register(regs, addr_offset); ret = get_eff_addr_modrm(insn, regs, &regoff, &eff_addr);
} if (ret)
goto out;
} }
eff_addr += insn->displacement.value;
} }
ret = get_seg_base_limit(insn, regs, addr_offset, &seg_base, NULL); ret = get_seg_base_limit(insn, regs, regoff, &seg_base, NULL);
if (ret) if (ret)
goto out; goto out;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment