From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752346AbdKHLHg (ORCPT ); Wed, 8 Nov 2017 06:07:36 -0500 Received: from terminus.zytor.com ([65.50.211.136]:36141 "EHLO terminus.zytor.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752308AbdKHLHd (ORCPT ); Wed, 8 Nov 2017 06:07:33 -0500 Date: Wed, 8 Nov 2017 02:59:26 -0800 From: tip-bot for Ricardo Neri Message-ID: Cc: shuah@kernel.org, torvalds@linux-foundation.org, thgarnie@google.com, corbet@lwn.net, keescook@chromium.org, ray.huang@amd.com, tglx@linutronix.de, qiaowei.ren@intel.com, linux-kernel@vger.kernel.org, cmetcalf@mellanox.com, akpm@linux-foundation.org, luto@kernel.org, lstoakes@gmail.com, jslaby@suse.cz, peterz@infradead.org, colin.king@canonical.com, dave.hansen@linux.intel.com, acme@redhat.com, vbabka@suse.cz, ricardo.neri-calderon@linux.intel.com, hpa@zytor.com, ravi.v.shankar@intel.com, mst@redhat.com, pbonzini@redhat.com, adrian.hunter@intel.com, bp@alien8.de, mingo@kernel.org, adam.buchbinder@gmail.com, brgerst@gmail.com, dvlasenk@redhat.com, tony.luck@intel.com, dvyukov@google.com, bp@suse.de, mhiramat@kernel.org, jpoimboe@redhat.com, paul.gortmaker@windriver.com, slaoub@gmail.com Reply-To: lstoakes@gmail.com, peterz@infradead.org, jslaby@suse.cz, dave.hansen@linux.intel.com, colin.king@canonical.com, akpm@linux-foundation.org, luto@kernel.org, ray.huang@amd.com, cmetcalf@mellanox.com, linux-kernel@vger.kernel.org, qiaowei.ren@intel.com, tglx@linutronix.de, shuah@kernel.org, keescook@chromium.org, thgarnie@google.com, torvalds@linux-foundation.org, corbet@lwn.net, dvyukov@google.com, tony.luck@intel.com, mhiramat@kernel.org, bp@suse.de, paul.gortmaker@windriver.com, jpoimboe@redhat.com, slaoub@gmail.com, adam.buchbinder@gmail.com, mingo@kernel.org, bp@alien8.de, brgerst@gmail.com, dvlasenk@redhat.com, ravi.v.shankar@intel.com, mst@redhat.com, pbonzini@redhat.com, adrian.hunter@intel.com, acme@redhat.com, vbabka@suse.cz, hpa@zytor.com, ricardo.neri-calderon@linux.intel.com In-Reply-To: <1509935277-22138-6-git-send-email-ricardo.neri-calderon@linux.intel.com> References: <1509935277-22138-6-git-send-email-ricardo.neri-calderon@linux.intel.com> To: linux-tip-commits@vger.kernel.org Subject: [tip:x86/asm] x86/insn-eval: Add support to resolve 16-bit address encodings Git-Commit-ID: 9c6c799faeed54b17857c2eed9058a25b8ee3614 X-Mailer: tip-git-log-daemon Robot-ID: Robot-Unsubscribe: Contact to get blacklisted from these emails MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset=UTF-8 Content-Disposition: inline Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Commit-ID: 9c6c799faeed54b17857c2eed9058a25b8ee3614 Gitweb: https://git.kernel.org/tip/9c6c799faeed54b17857c2eed9058a25b8ee3614 Author: Ricardo Neri AuthorDate: Sun, 5 Nov 2017 18:27:50 -0800 Committer: Ingo Molnar CommitDate: Wed, 8 Nov 2017 11:16:20 +0100 x86/insn-eval: Add support to resolve 16-bit address encodings Tasks running in virtual-8086 mode, in protected mode with code segment descriptors that specify 16-bit default address sizes via the D bit, or via an address override prefix will use 16-bit addressing form encodings as described in the Intel 64 and IA-32 Architecture Software Developer's Manual Volume 2A Section 2.1.5, Table 2-1. 16-bit addressing encodings differ in several ways from the 32-bit/64-bit addressing form encodings: ModRM.rm points to different registers and, in some cases, effective addresses are indicated by the addition of the value of two registers. Also, there is no support for SIB bytes. Thus, a separate function is needed to parse this form of addressing. Three functions are introduced. get_reg_offset_16() obtains the offset from the base of pt_regs of the registers indicated by the ModRM byte of the address encoding. get_eff_addr_modrm_16() computes the effective address from the value of the register operands. get_addr_ref_16() computes the linear address using the obtained effective address and the base address of the segment. Segment limits are enforced when running in protected mode. Signed-off-by: Ricardo Neri Reviewed-by: Thomas Gleixner Cc: Adam Buchbinder Cc: Adrian Hunter Cc: Andrew Morton Cc: Andy Lutomirski Cc: Arnaldo Carvalho de Melo Cc: Borislav Petkov Cc: Borislav Petkov Cc: Brian Gerst Cc: Chen Yucong Cc: Chris Metcalf Cc: Colin Ian King Cc: Dave Hansen Cc: Denys Vlasenko Cc: Dmitry Vyukov Cc: H. Peter Anvin Cc: Huang Rui Cc: Jiri Slaby Cc: Jonathan Corbet Cc: Josh Poimboeuf Cc: Kees Cook Cc: Linus Torvalds Cc: Lorenzo Stoakes Cc: Masami Hiramatsu Cc: Michael S. Tsirkin Cc: Paolo Bonzini Cc: Paul Gortmaker Cc: Peter Zijlstra Cc: Qiaowei Ren Cc: Ravi V. Shankar Cc: Shuah Khan Cc: Thomas Garnier Cc: Tony Luck Cc: Vlastimil Babka Cc: ricardo.neri@intel.com Link: http://lkml.kernel.org/r/1509935277-22138-6-git-send-email-ricardo.neri-calderon@linux.intel.com Signed-off-by: Ingo Molnar --- arch/x86/lib/insn-eval.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 212 insertions(+), 1 deletion(-) diff --git a/arch/x86/lib/insn-eval.c b/arch/x86/lib/insn-eval.c index ef102db..35625d2 100644 --- a/arch/x86/lib/insn-eval.c +++ b/arch/x86/lib/insn-eval.c @@ -481,6 +481,80 @@ static int get_reg_offset(struct insn *insn, struct pt_regs *regs, } /** + * get_reg_offset_16() - Obtain offset of register indicated by instruction + * @insn: Instruction containing ModRM byte + * @regs: Register values as seen when entering kernel mode + * @offs1: Offset of the first operand register + * @offs2: Offset of the second opeand register, if applicable + * + * Obtain the offset, in pt_regs, of the registers indicated by the ModRM byte + * in @insn. This function is to be used with 16-bit address encodings. The + * @offs1 and @offs2 will be written with the offset of the two registers + * indicated by the instruction. In cases where any of the registers is not + * referenced by the instruction, the value will be set to -EDOM. + * + * Returns: + * + * 0 on success, -EINVAL on error. + */ +static int get_reg_offset_16(struct insn *insn, struct pt_regs *regs, + int *offs1, int *offs2) +{ + /* + * 16-bit addressing can use one or two registers. Specifics of + * encodings are given in Table 2-1. "16-Bit Addressing Forms with the + * ModR/M Byte" of the Intel Software Development Manual. + */ + static const int regoff1[] = { + offsetof(struct pt_regs, bx), + offsetof(struct pt_regs, bx), + offsetof(struct pt_regs, bp), + offsetof(struct pt_regs, bp), + offsetof(struct pt_regs, si), + offsetof(struct pt_regs, di), + offsetof(struct pt_regs, bp), + offsetof(struct pt_regs, bx), + }; + + static const int regoff2[] = { + offsetof(struct pt_regs, si), + offsetof(struct pt_regs, di), + offsetof(struct pt_regs, si), + offsetof(struct pt_regs, di), + -EDOM, + -EDOM, + -EDOM, + -EDOM, + }; + + if (!offs1 || !offs2) + return -EINVAL; + + /* Operand is a register, use the generic function. */ + if (X86_MODRM_MOD(insn->modrm.value) == 3) { + *offs1 = insn_get_modrm_rm_off(insn, regs); + *offs2 = -EDOM; + return 0; + } + + *offs1 = regoff1[X86_MODRM_RM(insn->modrm.value)]; + *offs2 = regoff2[X86_MODRM_RM(insn->modrm.value)]; + + /* + * If ModRM.mod is 0 and ModRM.rm is 110b, then we use displacement- + * only addressing. This means that no registers are involved in + * computing the effective address. Thus, ensure that the first + * register offset is invalild. The second register offset is already + * invalid under the aforementioned conditions. + */ + if ((X86_MODRM_MOD(insn->modrm.value) == 0) && + (X86_MODRM_RM(insn->modrm.value) == 6)) + *offs1 = -EDOM; + + return 0; +} + +/** * get_desc() - Obtain pointer to a segment descriptor * @sel: Segment selector * @@ -815,7 +889,9 @@ static int get_eff_addr_reg(struct insn *insn, struct pt_regs *regs, return -EINVAL; /* Ignore bytes that are outside the address size. */ - if (insn->addr_bytes == 4) + if (insn->addr_bytes == 2) + *eff_addr = regs_get_register(regs, *regoff) & 0xffff; + else if (insn->addr_bytes == 4) *eff_addr = regs_get_register(regs, *regoff) & 0xffffffff; else /* 64-bit address */ *eff_addr = regs_get_register(regs, *regoff); @@ -891,6 +967,74 @@ static int get_eff_addr_modrm(struct insn *insn, struct pt_regs *regs, } /** + * get_eff_addr_modrm_16() - 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 16-bit 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_16(struct insn *insn, struct pt_regs *regs, + int *regoff, short *eff_addr) +{ + int addr_offset1, addr_offset2, ret; + short addr1 = 0, addr2 = 0, displacement; + + if (insn->addr_bytes != 2) + return -EINVAL; + + insn_get_modrm(insn); + + if (!insn->modrm.nbytes) + return -EINVAL; + + if (X86_MODRM_MOD(insn->modrm.value) > 2) + return -EINVAL; + + ret = get_reg_offset_16(insn, regs, &addr_offset1, &addr_offset2); + if (ret < 0) + return -EINVAL; + + /* + * Don't fail on invalid offset values. They might be invalid because + * they cannot be used for this particular value of ModRM. Instead, use + * them in the computation only if they contain a valid value. + */ + if (addr_offset1 != -EDOM) + addr1 = regs_get_register(regs, addr_offset1) & 0xffff; + + if (addr_offset2 != -EDOM) + addr2 = regs_get_register(regs, addr_offset2) & 0xffff; + + displacement = insn->displacement.value & 0xffff; + *eff_addr = addr1 + addr2 + displacement; + + /* + * The first operand register could indicate to use of either SS or DS + * registers to obtain the segment selector. The second operand + * register can only indicate the use of DS. Thus, the first operand + * will be used to obtain the segment selector. + */ + *regoff = addr_offset1; + + 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 @@ -975,6 +1119,71 @@ static int get_eff_addr_sib(struct insn *insn, struct pt_regs *regs, } /** + * get_addr_ref_16() - Obtain the 16-bit address referred by instruction + * @insn: Instruction containing ModRM byte and displacement + * @regs: Register values as seen when entering kernel mode + * + * This function is to be used with 16-bit address encodings. Obtain the memory + * address referred by the instruction's ModRM and displacement bytes. Also, the + * segment used as base is determined by either any segment override prefixes in + * @insn or the default segment of the registers involved in the address + * computation. In protected mode, segment limits are enforced. + * + * Returns: + * + * Linear address referenced by the instruction operands on success. + * + * -1L on error. + */ +static void __user *get_addr_ref_16(struct insn *insn, struct pt_regs *regs) +{ + unsigned long linear_addr = -1L, seg_base, seg_limit; + int ret, regoff; + short eff_addr; + long tmp; + + insn_get_modrm(insn); + insn_get_displacement(insn); + + if (insn->addr_bytes != 2) + goto out; + + if (X86_MODRM_MOD(insn->modrm.value) == 3) { + ret = get_eff_addr_reg(insn, regs, ®off, &tmp); + if (ret) + goto out; + + eff_addr = tmp; + } else { + ret = get_eff_addr_modrm_16(insn, regs, ®off, &eff_addr); + if (ret) + goto out; + } + + ret = get_seg_base_limit(insn, regs, regoff, &seg_base, &seg_limit); + if (ret) + goto out; + + /* + * Before computing the linear address, make sure the effective address + * is within the limits of the segment. In virtual-8086 mode, segment + * limits are not enforced. In such a case, the segment limit is -1L to + * reflect this fact. + */ + if ((unsigned long)(eff_addr & 0xffff) > seg_limit) + goto out; + + linear_addr = (unsigned long)(eff_addr & 0xffff) + seg_base; + + /* Limit linear address to 20 bits */ + if (v8086_mode(regs)) + linear_addr &= 0xfffff; + +out: + return (void __user *)linear_addr; +} + +/** * get_addr_ref_32() - Obtain a 32-bit linear address * @insn: Instruction with ModRM, SIB bytes and displacement * @regs: Register values as seen when entering kernel mode @@ -1143,6 +1352,8 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs) return (void __user *)-1L; switch (insn->addr_bytes) { + case 2: + return get_addr_ref_16(insn, regs); case 4: return get_addr_ref_32(insn, regs); case 8: