lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250923050942.206116-15-Neeraj.Upadhyay@amd.com>
Date: Tue, 23 Sep 2025 10:39:21 +0530
From: Neeraj Upadhyay <Neeraj.Upadhyay@....com>
To: <kvm@...r.kernel.org>, <seanjc@...gle.com>, <pbonzini@...hat.com>
CC: <linux-kernel@...r.kernel.org>, <Thomas.Lendacky@....com>,
	<nikunj@....com>, <Santosh.Shukla@....com>, <Vasant.Hegde@....com>,
	<Suravee.Suthikulpanit@....com>, <bp@...en8.de>, <David.Kaplan@....com>,
	<huibo.wang@....com>, <naveen.rao@....com>, <pgonda@...gle.com>,
	<linux-kselftest@...r.kernel.org>, <shuah@...nel.org>, <tiala@...rosoft.com>
Subject: [RFC PATCH v2 14/35] KVM: selftests: Restrict instruction decoder to x86_64 only

The insn-eval.c library was recently imported from the kernel source
and contains a significant amount of code to support legacy x86
operating modes, such as 32-bit protected mode, 16-bit addressing, and
v8086 mode.

The KVM selftests, particularly for features like SEV-ES, exclusively
target the 64-bit architecture with guest kernel running in kernel
mode.

Simplify the decoder by removing all logic not relevant to 64-bit mode.
This involves:
- Remove all CONFIG_X86_32 and v8086_mode conditional logic.
- Simplify resolve_default_seg to always assume 64-bit
  segmentation rules (i.e., most segments are ignored).
- Rework insn_get_seg_base to only handle the 64-bit model where
  FS/GS bases are read from MSRs.
- Delete support for 16-bit address decoding.
- Remove complex segment descriptor lookups, which are not used in the
  64-bit flat memory model.

This makes the library smaller and easier to maintain for its intended
purpose within the selftests.

Signed-off-by: Neeraj Upadhyay <Neeraj.Upadhyay@....com>
---
 .../testing/selftests/kvm/lib/x86/insn-eval.c | 254 ++----------------
 1 file changed, 28 insertions(+), 226 deletions(-)

diff --git a/tools/testing/selftests/kvm/lib/x86/insn-eval.c b/tools/testing/selftests/kvm/lib/x86/insn-eval.c
index a47c01977e72..cf751e4e36ec 100644
--- a/tools/testing/selftests/kvm/lib/x86/insn-eval.c
+++ b/tools/testing/selftests/kvm/lib/x86/insn-eval.c
@@ -178,52 +178,7 @@ static bool check_seg_overrides(struct insn *insn, int regoff)
  */
 static int resolve_default_seg(struct insn *insn, struct pt_regs *regs, int off)
 {
-	if (any_64bit_mode(regs))
-		return INAT_SEG_REG_IGNORE;
-	/*
-	 * Resolve the default segment register as described in Section 3.7.4
-	 * of the Intel Software Development Manual Vol. 1:
-	 *
-	 *  + 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 rSP or rBP.
-	 *  + CS for rIP.
-	 */
-
-	switch (off) {
-	case offsetof(struct pt_regs, ax):
-	case offsetof(struct pt_regs, cx):
-	case offsetof(struct pt_regs, dx):
-		/* Need insn to verify address size. */
-		if (insn->addr_bytes == 2)
-			return -EINVAL;
-
-		fallthrough;
-
-	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):
-		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;
-
-	default:
-		return -EINVAL;
-	}
+	return INAT_SEG_REG_IGNORE;
 }
 
 /**
@@ -288,12 +243,8 @@ static int resolve_seg_reg(struct insn *insn, struct pt_regs *regs, int regoff)
 	 * be used. Hence, it is not necessary to inspect the instruction,
 	 * which may be invalid at this point.
 	 */
-	if (regoff == offsetof(struct pt_regs, ip)) {
-		if (any_64bit_mode(regs))
-			return INAT_SEG_REG_IGNORE;
-		else
-			return INAT_SEG_REG_CS;
-	}
+	if (regoff == offsetof(struct pt_regs, ip))
+		return INAT_SEG_REG_IGNORE;
 
 	if (!insn)
 		return -EINVAL;
@@ -312,11 +263,8 @@ static int resolve_seg_reg(struct insn *insn, struct pt_regs *regs, int regoff)
 	 * In long mode, segment override prefixes are ignored, except for
 	 * overrides for FS and GS.
 	 */
-	if (any_64bit_mode(regs)) {
-		if (idx != INAT_SEG_REG_FS &&
-		    idx != INAT_SEG_REG_GS)
-			idx = INAT_SEG_REG_IGNORE;
-	}
+	if (idx != INAT_SEG_REG_FS && idx != INAT_SEG_REG_GS)
+		idx = INAT_SEG_REG_IGNORE;
 
 	return idx;
 }
@@ -327,11 +275,9 @@ static int resolve_seg_reg(struct insn *insn, struct pt_regs *regs, int regoff)
  * @seg_reg_idx:	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
- * 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. 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 X86_64 segment registers are ignored.
  *
  * Returns:
  *
@@ -344,7 +290,6 @@ static short get_segment_selector(struct pt_regs *regs, int seg_reg_idx)
 {
 	unsigned short sel;
 
-#ifdef CONFIG_X86_64
 	switch (seg_reg_idx) {
 	case INAT_SEG_REG_IGNORE:
 		return 0;
@@ -367,48 +312,6 @@ static short get_segment_selector(struct pt_regs *regs, int seg_reg_idx)
 	default:
 		return -EINVAL;
 	}
-#else /* CONFIG_X86_32 */
-	struct kernel_vm86_regs *vm86regs = (struct kernel_vm86_regs *)regs;
-
-	if (v8086_mode(regs)) {
-		switch (seg_reg_idx) {
-		case INAT_SEG_REG_CS:
-			return (unsigned short)(regs->cs & 0xffff);
-		case INAT_SEG_REG_SS:
-			return (unsigned short)(regs->ss & 0xffff);
-		case INAT_SEG_REG_DS:
-			return vm86regs->ds;
-		case INAT_SEG_REG_ES:
-			return vm86regs->es;
-		case INAT_SEG_REG_FS:
-			return vm86regs->fs;
-		case INAT_SEG_REG_GS:
-			return vm86regs->gs;
-		case INAT_SEG_REG_IGNORE:
-		default:
-			return -EINVAL;
-		}
-	}
-
-	switch (seg_reg_idx) {
-	case INAT_SEG_REG_CS:
-		return (unsigned short)(regs->cs & 0xffff);
-	case INAT_SEG_REG_SS:
-		return (unsigned short)(regs->ss & 0xffff);
-	case INAT_SEG_REG_DS:
-		return (unsigned short)(regs->ds & 0xffff);
-	case INAT_SEG_REG_ES:
-		return (unsigned short)(regs->es & 0xffff);
-	case INAT_SEG_REG_FS:
-		return (unsigned short)(regs->fs & 0xffff);
-	case INAT_SEG_REG_GS:
-		savesegment(gs, sel);
-		return sel;
-	case INAT_SEG_REG_IGNORE:
-	default:
-		return -EINVAL;
-	}
-#endif /* CONFIG_X86_64 */
 }
 
 static const int pt_regoff[] = {
@@ -420,7 +323,6 @@ static const int pt_regoff[] = {
 	offsetof(struct pt_regs, bp),
 	offsetof(struct pt_regs, si),
 	offsetof(struct pt_regs, di),
-#ifdef CONFIG_X86_64
 	offsetof(struct pt_regs, r8),
 	offsetof(struct pt_regs, r9),
 	offsetof(struct pt_regs, r10),
@@ -429,12 +331,6 @@ static const int pt_regoff[] = {
 	offsetof(struct pt_regs, r13),
 	offsetof(struct pt_regs, r14),
 	offsetof(struct pt_regs, r15),
-#else
-	offsetof(struct pt_regs, ds),
-	offsetof(struct pt_regs, es),
-	offsetof(struct pt_regs, fs),
-	offsetof(struct pt_regs, gs),
-#endif
 };
 
 int pt_regs_offset(struct pt_regs *regs, int regno)
@@ -453,9 +349,6 @@ static int get_regno(struct insn *insn, enum reg_type type)
 	 * Don't possibly decode a 32-bit instructions as
 	 * reading a 64-bit-only register.
 	 */
-	if (IS_ENABLED(CONFIG_X86_64) && !insn->x86_64)
-		nr_registers -= 8;
-
 	switch (type) {
 	case REG_TYPE_RM:
 		regno = X86_MODRM_RM(insn->modrm.value);
@@ -687,52 +580,33 @@ static bool get_desc(struct desc_struct *out, unsigned short sel)
  */
 unsigned long insn_get_seg_base(struct pt_regs *regs, int seg_reg_idx)
 {
-	struct desc_struct desc;
+	unsigned long base;
 	short sel;
 
 	sel = get_segment_selector(regs, seg_reg_idx);
 	if (sel < 0)
 		return -1L;
 
-	if (v8086_mode(regs))
-		/*
-		 * Base is simply the segment selector shifted 4
-		 * bits to the right.
-		 */
-		return (unsigned long)(sel << 4);
+	/*
+	 * Only FS or GS will have a base address, the rest of
+	 * the segments' bases are forced to 0.
+	 */
 
-	if (any_64bit_mode(regs)) {
+	if (seg_reg_idx == INAT_SEG_REG_FS) {
+		rdmsrq(MSR_FS_BASE, base);
+	} else if (seg_reg_idx == INAT_SEG_REG_GS) {
 		/*
-		 * Only FS or GS will have a base address, the rest of
-		 * the segments' bases are forced to 0.
+		 * swapgs was called at the kernel entry point. Thus,
+		 * MSR_KERNEL_GS_BASE will have the user-space GS base.
 		 */
-		unsigned long base;
-
-		if (seg_reg_idx == INAT_SEG_REG_FS) {
-			rdmsrq(MSR_FS_BASE, base);
-		} else if (seg_reg_idx == INAT_SEG_REG_GS) {
-			/*
-			 * swapgs was called at the kernel entry point. Thus,
-			 * MSR_KERNEL_GS_BASE will have the user-space GS base.
-			 */
-			if (user_mode(regs))
-				rdmsrq(MSR_KERNEL_GS_BASE, base);
-			else
-				rdmsrq(MSR_GS_BASE, base);
-		} else {
-			base = 0;
-		}
-		return base;
+		if (user_mode(regs))
+			rdmsrq(MSR_KERNEL_GS_BASE, base);
+		else
+			rdmsrq(MSR_GS_BASE, base);
+	} else {
+		base = 0;
 	}
-
-	/* In protected mode the segment selector cannot be null. */
-	if (!sel)
-		return -1L;
-
-	if (!get_desc(&desc, sel))
-		return -1L;
-
-	return get_desc_base(&desc);
+	return base;
 }
 
 /**
@@ -762,26 +636,7 @@ static unsigned long get_seg_limit(struct pt_regs *regs, int seg_reg_idx)
 	if (sel < 0)
 		return 0;
 
-	if (any_64bit_mode(regs) || v8086_mode(regs))
-		return -1L;
-
-	if (!sel)
-		return 0;
-
-	if (!get_desc(&desc, sel))
-		return 0;
-
-	/*
-	 * If the granularity bit is set, the limit is given in multiples
-	 * of 4096. This also means that the 12 least significant bits are
-	 * not tested when checking the segment limits. In practice,
-	 * this means that the segment ends in (limit << 12) + 0xfff.
-	 */
-	limit = get_desc_limit(&desc);
-	if (desc.g)
-		limit = (limit << 12) + 0xfff;
-
-	return limit;
+	return -1L;
 }
 
 /**
@@ -805,10 +660,6 @@ int insn_get_code_seg_params(struct pt_regs *regs)
 	struct desc_struct desc;
 	short sel;
 
-	if (v8086_mode(regs))
-		/* Address and operand size are both 16-bit. */
-		return INSN_CODE_SEG_PARAMS(2, 2);
-
 	sel = get_segment_selector(regs, INAT_SEG_REG_CS);
 	if (sel < 0)
 		return sel;
@@ -1042,10 +893,7 @@ static int get_eff_addr_modrm(struct insn *insn, struct pt_regs *regs,
 	 * following instruction.
 	 */
 	if (*regoff == -EDOM) {
-		if (any_64bit_mode(regs))
-			tmp = regs->ip + insn->length;
-		else
-			tmp = 0;
+		tmp = regs->ip + insn->length;
 	} else if (*regoff < 0) {
 		return -EINVAL;
 	} else {
@@ -1277,9 +1125,6 @@ static void __user *get_addr_ref_16(struct insn *insn, struct pt_regs *regs)
 
 	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;
@@ -1338,27 +1183,6 @@ static void __user *get_addr_ref_32(struct insn *insn, struct pt_regs *regs)
 	if (ret)
 		goto out;
 
-	/*
-	 * In protected mode, before computing the linear address, make sure
-	 * the effective address is within the limits of the segment.
-	 * 32-bit addresses can be used in long and virtual-8086 modes if an
-	 * address override prefix is used. In such cases, segment limits are
-	 * not enforced. When in virtual-8086 mode, the segment limit is -1L
-	 * to reflect this situation.
-	 *
-	 * After computed, the effective address is treated as an unsigned
-	 * quantity.
-	 */
-	if (!any_64bit_mode(regs) && ((unsigned int)eff_addr > seg_limit))
-		goto out;
-
-	/*
-	 * Even though 32-bit address encodings are allowed in virtual-8086
-	 * mode, the address range is still limited to [0x-0xffff].
-	 */
-	if (v8086_mode(regs) && (eff_addr & ~0xffff))
-		goto out;
-
 	/*
 	 * Data type long could be 64 bits in size. Ensure that our 32-bit
 	 * effective address is not sign-extended when computing the linear
@@ -1366,9 +1190,6 @@ static void __user *get_addr_ref_32(struct insn *insn, struct pt_regs *regs)
 	 */
 	linear_addr = (unsigned long)(eff_addr & 0xffffffff) + seg_base;
 
-	/* Limit linear address to 20 bits */
-	if (v8086_mode(regs))
-		linear_addr &= 0xfffff;
 
 out:
 	return (void __user *)linear_addr;
@@ -1389,12 +1210,6 @@ static void __user *get_addr_ref_32(struct insn *insn, struct pt_regs *regs)
  *
  * -1L on error.
  */
-#ifndef CONFIG_X86_64
-static void __user *get_addr_ref_64(struct insn *insn, struct pt_regs *regs)
-{
-	return (void __user *)-1L;
-}
-#else
 static void __user *get_addr_ref_64(struct insn *insn, struct pt_regs *regs)
 {
 	unsigned long linear_addr = -1L, seg_base;
@@ -1431,7 +1246,6 @@ static void __user *get_addr_ref_64(struct insn *insn, struct pt_regs *regs)
 out:
 	return (void __user *)linear_addr;
 }
-#endif /* CONFIG_X86_64 */
 
 /**
  * insn_get_addr_ref() - Obtain the linear address referred by instruction
@@ -1472,18 +1286,6 @@ int insn_get_effective_ip(struct pt_regs *regs, unsigned long *ip)
 {
 	unsigned long seg_base = 0;
 
-	/*
-	 * If not in user-space long mode, a custom code segment could be in
-	 * use. This is true in protected mode (if the process defined a local
-	 * descriptor table), or virtual-8086 mode. In most of the cases
-	 * seg_base will be zero as in USER_CS.
-	 */
-	if (!user_64bit_mode(regs)) {
-		seg_base = insn_get_seg_base(regs, INAT_SEG_REG_CS);
-		if (seg_base == -1L)
-			return -EINVAL;
-	}
-
 	*ip = seg_base + regs->ip;
 
 	return 0;
@@ -1563,7 +1365,7 @@ bool insn_decode_from_regs(struct insn *insn, struct pt_regs *regs,
 {
 	int seg_defs;
 
-	insn_init(insn, buf, buf_size, user_64bit_mode(regs));
+	insn_init(insn, buf, buf_size, true);
 
 	/*
 	 * Override the default operand and address sizes with what is specified
-- 
2.34.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ