Commit bffe30dd authored by Joerg Roedel's avatar Joerg Roedel Committed by Borislav Petkov
Browse files

x86/sev-es: Use __copy_from_user_inatomic()



The #VC handler must run in atomic context and cannot sleep. This is a
problem when it tries to fetch instruction bytes from user-space via
copy_from_user().

Introduce a insn_fetch_from_user_inatomic() helper which uses
__copy_from_user_inatomic() to safely copy the instruction bytes to
kernel memory in the #VC handler.

Fixes: 5e3427a7 ("x86/sev-es: Handle instruction fetches from user-space")
Signed-off-by: default avatarJoerg Roedel <jroedel@suse.de>
Signed-off-by: default avatarBorislav Petkov <bp@suse.de>
Cc: stable@vger.kernel.org # v5.10+
Link: https://lkml.kernel.org/r/20210303141716.29223-6-joro@8bytes.org
parent 62441a1f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ unsigned long insn_get_seg_base(struct pt_regs *regs, int seg_reg_idx);
int insn_get_code_seg_params(struct pt_regs *regs);
int insn_fetch_from_user(struct pt_regs *regs,
			 unsigned char buf[MAX_INSN_SIZE]);
int insn_fetch_from_user_inatomic(struct pt_regs *regs,
				  unsigned char buf[MAX_INSN_SIZE]);
bool insn_decode(struct insn *insn, struct pt_regs *regs,
		 unsigned char buf[MAX_INSN_SIZE], int buf_size);

+1 −1
Original line number Diff line number Diff line
@@ -258,7 +258,7 @@ static enum es_result vc_decode_insn(struct es_em_ctxt *ctxt)
	int res;

	if (user_mode(ctxt->regs)) {
		res = insn_fetch_from_user(ctxt->regs, buffer);
		res = insn_fetch_from_user_inatomic(ctxt->regs, buffer);
		if (!res) {
			ctxt->fi.vector     = X86_TRAP_PF;
			ctxt->fi.error_code = X86_PF_INSTR | X86_PF_USER;
+52 −14
Original line number Diff line number Diff line
@@ -1415,6 +1415,25 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
	}
}

static unsigned long insn_get_effective_ip(struct pt_regs *regs)
{
	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 0;
	}

	return seg_base + regs->ip;
}

/**
 * insn_fetch_from_user() - Copy instruction bytes from user-space memory
 * @regs:	Structure with register values as seen when entering kernel mode
@@ -1431,24 +1450,43 @@ void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
 */
int insn_fetch_from_user(struct pt_regs *regs, unsigned char buf[MAX_INSN_SIZE])
{
	unsigned long seg_base = 0;
	unsigned long ip;
	int not_copied;

	/*
	 * 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)
	ip = insn_get_effective_ip(regs);
	if (!ip)
		return 0;

	not_copied = copy_from_user(buf, (void __user *)ip, MAX_INSN_SIZE);

	return MAX_INSN_SIZE - not_copied;
}

/**
 * insn_fetch_from_user_inatomic() - Copy instruction bytes from user-space memory
 *                                   while in atomic code
 * @regs:	Structure with register values as seen when entering kernel mode
 * @buf:	Array to store the fetched instruction
 *
 * Gets the linear address of the instruction and copies the instruction bytes
 * to the buf. This function must be used in atomic context.
 *
 * Returns:
 *
 * Number of instruction bytes copied.
 *
 * 0 if nothing was copied.
 */
int insn_fetch_from_user_inatomic(struct pt_regs *regs, unsigned char buf[MAX_INSN_SIZE])
{
	unsigned long ip;
	int not_copied;

	ip = insn_get_effective_ip(regs);
	if (!ip)
		return 0;

	not_copied = copy_from_user(buf, (void __user *)(seg_base + regs->ip),
				    MAX_INSN_SIZE);
	not_copied = __copy_from_user_inatomic(buf, (void __user *)ip, MAX_INSN_SIZE);

	return MAX_INSN_SIZE - not_copied;
}