Joerg Roedel 8bcc6e
From: Joerg Roedel <jroedel@suse.de>
Joerg Roedel 8bcc6e
Date: Wed, 3 Mar 2021 15:17:16 +0100
Joerg Roedel 8bcc6e
Subject: x86/sev-es: Use __copy_from_user_inatomic()
Joerg Roedel 8bcc6e
Git-commit: bffe30dd9f1f3b2608a87ac909a224d6be472485
Joerg Roedel 8bcc6e
Patch-mainline: v5.12-rc3
Joerg Roedel 8bcc6e
References: bsc#1183553
Joerg Roedel 8bcc6e
Joerg Roedel 8bcc6e
The #VC handler must run in atomic context and cannot sleep. This is a
Joerg Roedel 8bcc6e
problem when it tries to fetch instruction bytes from user-space via
Joerg Roedel 8bcc6e
copy_from_user().
Joerg Roedel 8bcc6e
Joerg Roedel 8bcc6e
Introduce a insn_fetch_from_user_inatomic() helper which uses
Joerg Roedel 8bcc6e
__copy_from_user_inatomic() to safely copy the instruction bytes to
Joerg Roedel 8bcc6e
kernel memory in the #VC handler.
Joerg Roedel 8bcc6e
Joerg Roedel 8bcc6e
Fixes: 5e3427a7bc432 ("x86/sev-es: Handle instruction fetches from user-space")
Joerg Roedel 8bcc6e
Signed-off-by: Joerg Roedel <jroedel@suse.de>
Joerg Roedel 8bcc6e
Signed-off-by: Borislav Petkov <bp@suse.de>
Joerg Roedel 8bcc6e
Cc: stable@vger.kernel.org # v5.10+
Joerg Roedel 8bcc6e
Link: https://lkml.kernel.org/r/20210303141716.29223-6-joro@8bytes.org
Joerg Roedel 8bcc6e
---
Joerg Roedel 8bcc6e
 arch/x86/include/asm/insn-eval.h |    2 +
Joerg Roedel 8bcc6e
 arch/x86/kernel/sev-es.c         |    2 -
Joerg Roedel 8bcc6e
 arch/x86/lib/insn-eval.c         |   66 ++++++++++++++++++++++++++++++---------
Joerg Roedel 8bcc6e
 3 files changed, 55 insertions(+), 15 deletions(-)
Joerg Roedel 8bcc6e
Joerg Roedel 8bcc6e
--- a/arch/x86/include/asm/insn-eval.h
Joerg Roedel 8bcc6e
+++ b/arch/x86/include/asm/insn-eval.h
Joerg Roedel 8bcc6e
@@ -23,6 +23,8 @@ unsigned long insn_get_seg_base(struct p
Joerg Roedel 8bcc6e
 int insn_get_code_seg_params(struct pt_regs *regs);
Joerg Roedel 8bcc6e
 int insn_fetch_from_user(struct pt_regs *regs,
Joerg Roedel 8bcc6e
 			 unsigned char buf[MAX_INSN_SIZE]);
Joerg Roedel 8bcc6e
+int insn_fetch_from_user_inatomic(struct pt_regs *regs,
Joerg Roedel 8bcc6e
+				  unsigned char buf[MAX_INSN_SIZE]);
Joerg Roedel 8bcc6e
 bool insn_decode(struct insn *insn, struct pt_regs *regs,
Joerg Roedel 8bcc6e
 		 unsigned char buf[MAX_INSN_SIZE], int buf_size);
Joerg Roedel 8bcc6e
 
Joerg Roedel 8bcc6e
--- a/arch/x86/kernel/sev-es.c
Joerg Roedel 8bcc6e
+++ b/arch/x86/kernel/sev-es.c
Joerg Roedel 8bcc6e
@@ -260,7 +260,7 @@ static enum es_result vc_decode_insn(str
Joerg Roedel 8bcc6e
 	int res;
Joerg Roedel 8bcc6e
 
Joerg Roedel 8bcc6e
 	if (user_mode(ctxt->regs)) {
Joerg Roedel 8bcc6e
-		res = insn_fetch_from_user(ctxt->regs, buffer);
Joerg Roedel 8bcc6e
+		res = insn_fetch_from_user_inatomic(ctxt->regs, buffer);
Joerg Roedel 8bcc6e
 		if (!res) {
Joerg Roedel 8bcc6e
 			ctxt->fi.vector     = X86_TRAP_PF;
Joerg Roedel 8bcc6e
 			ctxt->fi.error_code = X86_PF_INSTR | X86_PF_USER;
Joerg Roedel 8bcc6e
--- a/arch/x86/lib/insn-eval.c
Joerg Roedel 8bcc6e
+++ b/arch/x86/lib/insn-eval.c
Joerg Roedel 8bcc6e
@@ -1417,6 +1417,25 @@ void __user *insn_get_addr_ref(struct in
Joerg Roedel 8bcc6e
 	}
Joerg Roedel 8bcc6e
 }
Joerg Roedel 8bcc6e
 
Joerg Roedel 8bcc6e
+static unsigned long insn_get_effective_ip(struct pt_regs *regs)
Joerg Roedel 8bcc6e
+{
Joerg Roedel 8bcc6e
+	unsigned long seg_base = 0;
Joerg Roedel 8bcc6e
+
Joerg Roedel 8bcc6e
+	/*
Joerg Roedel 8bcc6e
+	 * If not in user-space long mode, a custom code segment could be in
Joerg Roedel 8bcc6e
+	 * use. This is true in protected mode (if the process defined a local
Joerg Roedel 8bcc6e
+	 * descriptor table), or virtual-8086 mode. In most of the cases
Joerg Roedel 8bcc6e
+	 * seg_base will be zero as in USER_CS.
Joerg Roedel 8bcc6e
+	 */
Joerg Roedel 8bcc6e
+	if (!user_64bit_mode(regs)) {
Joerg Roedel 8bcc6e
+		seg_base = insn_get_seg_base(regs, INAT_SEG_REG_CS);
Joerg Roedel 8bcc6e
+		if (seg_base == -1L)
Joerg Roedel 8bcc6e
+			return 0;
Joerg Roedel 8bcc6e
+	}
Joerg Roedel 8bcc6e
+
Joerg Roedel 8bcc6e
+	return seg_base + regs->ip;
Joerg Roedel 8bcc6e
+}
Joerg Roedel 8bcc6e
+
Joerg Roedel 8bcc6e
 /**
Joerg Roedel 8bcc6e
  * insn_fetch_from_user() - Copy instruction bytes from user-space memory
Joerg Roedel 8bcc6e
  * @regs:	Structure with register values as seen when entering kernel mode
Joerg Roedel 8bcc6e
@@ -1433,24 +1452,43 @@ void __user *insn_get_addr_ref(struct in
Joerg Roedel 8bcc6e
  */
Joerg Roedel 8bcc6e
 int insn_fetch_from_user(struct pt_regs *regs, unsigned char buf[MAX_INSN_SIZE])
Joerg Roedel 8bcc6e
 {
Joerg Roedel 8bcc6e
-	unsigned long seg_base = 0;
Joerg Roedel 8bcc6e
+	unsigned long ip;
Joerg Roedel 8bcc6e
 	int not_copied;
Joerg Roedel 8bcc6e
 
Joerg Roedel 8bcc6e
-	/*
Joerg Roedel 8bcc6e
-	 * If not in user-space long mode, a custom code segment could be in
Joerg Roedel 8bcc6e
-	 * use. This is true in protected mode (if the process defined a local
Joerg Roedel 8bcc6e
-	 * descriptor table), or virtual-8086 mode. In most of the cases
Joerg Roedel 8bcc6e
-	 * seg_base will be zero as in USER_CS.
Joerg Roedel 8bcc6e
-	 */
Joerg Roedel 8bcc6e
-	if (!user_64bit_mode(regs)) {
Joerg Roedel 8bcc6e
-		seg_base = insn_get_seg_base(regs, INAT_SEG_REG_CS);
Joerg Roedel 8bcc6e
-		if (seg_base == -1L)
Joerg Roedel 8bcc6e
-			return 0;
Joerg Roedel 8bcc6e
-	}
Joerg Roedel 8bcc6e
+	ip = insn_get_effective_ip(regs);
Joerg Roedel 8bcc6e
+	if (!ip)
Joerg Roedel 8bcc6e
+		return 0;
Joerg Roedel 8bcc6e
+
Joerg Roedel 8bcc6e
+	not_copied = copy_from_user(buf, (void __user *)ip, MAX_INSN_SIZE);
Joerg Roedel 8bcc6e
+
Joerg Roedel 8bcc6e
+	return MAX_INSN_SIZE - not_copied;
Joerg Roedel 8bcc6e
+}
Joerg Roedel 8bcc6e
+
Joerg Roedel 8bcc6e
+/**
Joerg Roedel 8bcc6e
+ * insn_fetch_from_user_inatomic() - Copy instruction bytes from user-space memory
Joerg Roedel 8bcc6e
+ *                                   while in atomic code
Joerg Roedel 8bcc6e
+ * @regs:	Structure with register values as seen when entering kernel mode
Joerg Roedel 8bcc6e
+ * @buf:	Array to store the fetched instruction
Joerg Roedel 8bcc6e
+ *
Joerg Roedel 8bcc6e
+ * Gets the linear address of the instruction and copies the instruction bytes
Joerg Roedel 8bcc6e
+ * to the buf. This function must be used in atomic context.
Joerg Roedel 8bcc6e
+ *
Joerg Roedel 8bcc6e
+ * Returns:
Joerg Roedel 8bcc6e
+ *
Joerg Roedel 8bcc6e
+ * Number of instruction bytes copied.
Joerg Roedel 8bcc6e
+ *
Joerg Roedel 8bcc6e
+ * 0 if nothing was copied.
Joerg Roedel 8bcc6e
+ */
Joerg Roedel 8bcc6e
+int insn_fetch_from_user_inatomic(struct pt_regs *regs, unsigned char buf[MAX_INSN_SIZE])
Joerg Roedel 8bcc6e
+{
Joerg Roedel 8bcc6e
+	unsigned long ip;
Joerg Roedel 8bcc6e
+	int not_copied;
Joerg Roedel 8bcc6e
 
Joerg Roedel 8bcc6e
+	ip = insn_get_effective_ip(regs);
Joerg Roedel 8bcc6e
+	if (!ip)
Joerg Roedel 8bcc6e
+		return 0;
Joerg Roedel 8bcc6e
 
Joerg Roedel 8bcc6e
-	not_copied = copy_from_user(buf, (void __user *)(seg_base + regs->ip),
Joerg Roedel 8bcc6e
-				    MAX_INSN_SIZE);
Joerg Roedel 8bcc6e
+	not_copied = __copy_from_user_inatomic(buf, (void __user *)ip, MAX_INSN_SIZE);
Joerg Roedel 8bcc6e
 
Joerg Roedel 8bcc6e
 	return MAX_INSN_SIZE - not_copied;
Joerg Roedel 8bcc6e
 }
Joerg Roedel 8bcc6e