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;