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: 5e3427a7bc
("x86/sev-es: Handle instruction fetches from user-space")
Signed-off-by: Joerg Roedel <jroedel@suse.de>
Signed-off-by: Borislav Petkov <bp@suse.de>
Cc: stable@vger.kernel.org # v5.10+
Link: https://lkml.kernel.org/r/20210303141716.29223-6-joro@8bytes.org
This commit is contained in:
parent
62441a1fb5
commit
bffe30dd9f
|
@ -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_get_code_seg_params(struct pt_regs *regs);
|
||||||
int insn_fetch_from_user(struct pt_regs *regs,
|
int insn_fetch_from_user(struct pt_regs *regs,
|
||||||
unsigned char buf[MAX_INSN_SIZE]);
|
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,
|
bool insn_decode(struct insn *insn, struct pt_regs *regs,
|
||||||
unsigned char buf[MAX_INSN_SIZE], int buf_size);
|
unsigned char buf[MAX_INSN_SIZE], int buf_size);
|
||||||
|
|
||||||
|
|
|
@ -258,7 +258,7 @@ static enum es_result vc_decode_insn(struct es_em_ctxt *ctxt)
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
if (user_mode(ctxt->regs)) {
|
if (user_mode(ctxt->regs)) {
|
||||||
res = insn_fetch_from_user(ctxt->regs, buffer);
|
res = insn_fetch_from_user_inatomic(ctxt->regs, buffer);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
ctxt->fi.vector = X86_TRAP_PF;
|
ctxt->fi.vector = X86_TRAP_PF;
|
||||||
ctxt->fi.error_code = X86_PF_INSTR | X86_PF_USER;
|
ctxt->fi.error_code = X86_PF_INSTR | X86_PF_USER;
|
||||||
|
|
|
@ -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
|
* insn_fetch_from_user() - Copy instruction bytes from user-space memory
|
||||||
* @regs: Structure with register values as seen when entering kernel mode
|
* @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])
|
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;
|
int not_copied;
|
||||||
|
|
||||||
/*
|
ip = insn_get_effective_ip(regs);
|
||||||
* If not in user-space long mode, a custom code segment could be in
|
if (!ip)
|
||||||
* use. This is true in protected mode (if the process defined a local
|
return 0;
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
not_copied = copy_from_user(buf, (void __user *)ip, MAX_INSN_SIZE);
|
||||||
|
|
||||||
not_copied = copy_from_user(buf, (void __user *)(seg_base + regs->ip),
|
return MAX_INSN_SIZE - not_copied;
|
||||||
MAX_INSN_SIZE);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_inatomic(buf, (void __user *)ip, MAX_INSN_SIZE);
|
||||||
|
|
||||||
return MAX_INSN_SIZE - not_copied;
|
return MAX_INSN_SIZE - not_copied;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue