aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/x86/lib/insn-eval.c55
1 files changed, 52 insertions, 3 deletions
diff --git a/arch/x86/lib/insn-eval.c b/arch/x86/lib/insn-eval.c
index 6bf819f923e7..1c23ec03c568 100644
--- a/arch/x86/lib/insn-eval.c
+++ b/arch/x86/lib/insn-eval.c
@@ -728,6 +728,43 @@ int insn_get_modrm_rm_off(struct insn *insn, struct pt_regs *regs)
return get_reg_offset(insn, regs, REG_TYPE_RM);
}
+/**
+ * get_seg_base_addr() - obtain base address of a segment
+ * @insn: Instruction. Must be valid.
+ * @regs: Register values as seen when entering kernel mode
+ * @regoff: Operand offset, in pt_regs, used to resolve segment descriptor
+ * @base: Obtained segment base
+ *
+ * Obtain the base address of the segment associated with the operand @regoff
+ * and, if any or allowed, override prefixes in @insn. This function is
+ * different from insn_get_seg_base() as the latter does not resolve the segment
+ * associated with the instruction operand.
+ *
+ * Returns:
+ *
+ * 0 on success. @base will contain the base address of the resolved segment.
+ *
+ * -EINVAL on error.
+ */
+static int get_seg_base_addr(struct insn *insn, struct pt_regs *regs,
+ int regoff, unsigned long *base)
+{
+ int seg_reg_idx;
+
+ if (!base)
+ return -EINVAL;
+
+ seg_reg_idx = resolve_seg_reg(insn, regs, regoff);
+ if (seg_reg_idx < 0)
+ return seg_reg_idx;
+
+ *base = insn_get_seg_base(regs, seg_reg_idx);
+ if (*base == -1L)
+ return -EINVAL;
+
+ return 0;
+}
+
/*
* return the address being referenced be instruction
* for rm=3 returning the content of the rm reg
@@ -735,8 +772,8 @@ int insn_get_modrm_rm_off(struct insn *insn, struct pt_regs *regs)
*/
void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
{
- int addr_offset, base_offset, indx_offset;
- unsigned long linear_addr = -1L;
+ int addr_offset, base_offset, indx_offset, ret;
+ unsigned long linear_addr = -1L, seg_base;
long eff_addr, base, indx;
insn_byte_t sib;
@@ -750,6 +787,7 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
goto out;
eff_addr = regs_get_register(regs, addr_offset);
+
} else {
if (insn->sib.nbytes) {
/*
@@ -776,6 +814,13 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
indx = regs_get_register(regs, indx_offset);
eff_addr = base + indx * (1 << X86_SIB_SCALE(sib));
+
+ /*
+ * The base determines the segment used to compute
+ * the linear address.
+ */
+ addr_offset = base_offset;
+
} else {
addr_offset = get_reg_offset(insn, regs, REG_TYPE_RM);
/*
@@ -798,7 +843,11 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
eff_addr += insn->displacement.value;
}
- linear_addr = (unsigned long)eff_addr;
+ ret = get_seg_base_addr(insn, regs, addr_offset, &seg_base);
+ if (ret)
+ goto out;
+
+ linear_addr = (unsigned long)eff_addr + seg_base;
out:
return (void __user *)linear_addr;