diff options
Diffstat (limited to 'arch/arm64/mm/fault.c')
| -rw-r--r-- | arch/arm64/mm/fault.c | 83 | 
1 files changed, 50 insertions, 33 deletions
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 1ee94002801f..29a6b8c9e830 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -40,7 +40,7 @@  #include <asm/traps.h>  struct fault_info { -	int	(*fn)(unsigned long addr, unsigned int esr, +	int	(*fn)(unsigned long far, unsigned int esr,  		      struct pt_regs *regs);  	int	sig;  	int	code; @@ -385,8 +385,11 @@ static void set_thread_esr(unsigned long address, unsigned int esr)  	current->thread.fault_code = esr;  } -static void do_bad_area(unsigned long addr, unsigned int esr, struct pt_regs *regs) +static void do_bad_area(unsigned long far, unsigned int esr, +			struct pt_regs *regs)  { +	unsigned long addr = untagged_addr(far); +  	/*  	 * If we are in kernel mode at this point, we have no context to  	 * handle this fault with. @@ -395,8 +398,7 @@ static void do_bad_area(unsigned long addr, unsigned int esr, struct pt_regs *re  		const struct fault_info *inf = esr_to_fault_info(esr);  		set_thread_esr(addr, esr); -		arm64_force_sig_fault(inf->sig, inf->code, (void __user *)addr, -				      inf->name); +		arm64_force_sig_fault(inf->sig, inf->code, far, inf->name);  	} else {  		__do_kernel_fault(addr, esr, regs);  	} @@ -448,7 +450,7 @@ static bool is_write_abort(unsigned int esr)  	return (esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM);  } -static int __kprobes do_page_fault(unsigned long addr, unsigned int esr, +static int __kprobes do_page_fault(unsigned long far, unsigned int esr,  				   struct pt_regs *regs)  {  	const struct fault_info *inf; @@ -456,6 +458,7 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,  	vm_fault_t fault;  	unsigned long vm_flags = VM_ACCESS_FLAGS;  	unsigned int mm_flags = FAULT_FLAG_DEFAULT; +	unsigned long addr = untagged_addr(far);  	if (kprobe_page_fault(regs, esr))  		return 0; @@ -567,8 +570,7 @@ retry:  		 * We had some memory, but were unable to successfully fix up  		 * this page fault.  		 */ -		arm64_force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)addr, -				      inf->name); +		arm64_force_sig_fault(SIGBUS, BUS_ADRERR, far, inf->name);  	} else if (fault & (VM_FAULT_HWPOISON_LARGE | VM_FAULT_HWPOISON)) {  		unsigned int lsb; @@ -576,8 +578,7 @@ retry:  		if (fault & VM_FAULT_HWPOISON_LARGE)  			lsb = hstate_index_to_shift(VM_FAULT_GET_HINDEX(fault)); -		arm64_force_sig_mceerr(BUS_MCEERR_AR, (void __user *)addr, lsb, -				       inf->name); +		arm64_force_sig_mceerr(BUS_MCEERR_AR, far, lsb, inf->name);  	} else {  		/*  		 * Something tried to access memory that isn't in our memory @@ -585,8 +586,7 @@ retry:  		 */  		arm64_force_sig_fault(SIGSEGV,  				      fault == VM_FAULT_BADACCESS ? SEGV_ACCERR : SEGV_MAPERR, -				      (void __user *)addr, -				      inf->name); +				      far, inf->name);  	}  	return 0; @@ -596,33 +596,35 @@ no_context:  	return 0;  } -static int __kprobes do_translation_fault(unsigned long addr, +static int __kprobes do_translation_fault(unsigned long far,  					  unsigned int esr,  					  struct pt_regs *regs)  { +	unsigned long addr = untagged_addr(far); +  	if (is_ttbr0_addr(addr)) -		return do_page_fault(addr, esr, regs); +		return do_page_fault(far, esr, regs); -	do_bad_area(addr, esr, regs); +	do_bad_area(far, esr, regs);  	return 0;  } -static int do_alignment_fault(unsigned long addr, unsigned int esr, +static int do_alignment_fault(unsigned long far, unsigned int esr,  			      struct pt_regs *regs)  { -	do_bad_area(addr, esr, regs); +	do_bad_area(far, esr, regs);  	return 0;  } -static int do_bad(unsigned long addr, unsigned int esr, struct pt_regs *regs) +static int do_bad(unsigned long far, unsigned int esr, struct pt_regs *regs)  {  	return 1; /* "fault" */  } -static int do_sea(unsigned long addr, unsigned int esr, struct pt_regs *regs) +static int do_sea(unsigned long far, unsigned int esr, struct pt_regs *regs)  {  	const struct fault_info *inf; -	void __user *siaddr; +	unsigned long siaddr;  	inf = esr_to_fault_info(esr); @@ -634,19 +636,30 @@ static int do_sea(unsigned long addr, unsigned int esr, struct pt_regs *regs)  		return 0;  	} -	if (esr & ESR_ELx_FnV) -		siaddr = NULL; -	else -		siaddr  = (void __user *)addr; +	if (esr & ESR_ELx_FnV) { +		siaddr = 0; +	} else { +		/* +		 * The architecture specifies that the tag bits of FAR_EL1 are +		 * UNKNOWN for synchronous external aborts. Mask them out now +		 * so that userspace doesn't see them. +		 */ +		siaddr  = untagged_addr(far); +	}  	arm64_notify_die(inf->name, regs, inf->sig, inf->code, siaddr, esr);  	return 0;  } -static int do_tag_check_fault(unsigned long addr, unsigned int esr, +static int do_tag_check_fault(unsigned long far, unsigned int esr,  			      struct pt_regs *regs)  { -	do_bad_area(addr, esr, regs); +	/* +	 * The architecture specifies that bits 63:60 of FAR_EL1 are UNKNOWN for tag +	 * check faults. Mask them out now so that userspace doesn't see them. +	 */ +	far &= (1UL << 60) - 1; +	do_bad_area(far, esr, regs);  	return 0;  } @@ -717,11 +730,12 @@ static const struct fault_info fault_info[] = {  	{ do_bad,		SIGKILL, SI_KERNEL,	"unknown 63"			},  }; -void do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs) +void do_mem_abort(unsigned long far, unsigned int esr, struct pt_regs *regs)  {  	const struct fault_info *inf = esr_to_fault_info(esr); +	unsigned long addr = untagged_addr(far); -	if (!inf->fn(addr, esr, regs)) +	if (!inf->fn(far, esr, regs))  		return;  	if (!user_mode(regs)) { @@ -730,8 +744,12 @@ void do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs)  		show_pte(addr);  	} -	arm64_notify_die(inf->name, regs, -			 inf->sig, inf->code, (void __user *)addr, esr); +	/* +	 * At this point we have an unrecognized fault type whose tag bits may +	 * have been defined as UNKNOWN. Therefore we only expose the untagged +	 * address to the signal handler. +	 */ +	arm64_notify_die(inf->name, regs, inf->sig, inf->code, addr, esr);  }  NOKPROBE_SYMBOL(do_mem_abort); @@ -744,8 +762,8 @@ NOKPROBE_SYMBOL(do_el0_irq_bp_hardening);  void do_sp_pc_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs)  { -	arm64_notify_die("SP/PC alignment exception", regs, -			 SIGBUS, BUS_ADRALN, (void __user *)addr, esr); +	arm64_notify_die("SP/PC alignment exception", regs, SIGBUS, BUS_ADRALN, +			 addr, esr);  }  NOKPROBE_SYMBOL(do_sp_pc_abort); @@ -871,8 +889,7 @@ void do_debug_exception(unsigned long addr_if_watchpoint, unsigned int esr,  		arm64_apply_bp_hardening();  	if (inf->fn(addr_if_watchpoint, esr, regs)) { -		arm64_notify_die(inf->name, regs, -				 inf->sig, inf->code, (void __user *)pc, esr); +		arm64_notify_die(inf->name, regs, inf->sig, inf->code, pc, esr);  	}  	debug_exception_exit(regs);  |