| From 09a6adf53d42ca3088fa3fb41f40b768efc711ed Mon Sep 17 00:00:00 2001 |
| From: Victor Kamensky <kamensky@cisco.com> |
| Date: Mon, 3 Apr 2017 22:51:01 -0700 |
| Subject: arm64: mm: unaligned access by user-land should be received as SIGBUS |
| |
| From: Victor Kamensky <kamensky@cisco.com> |
| |
| commit 09a6adf53d42ca3088fa3fb41f40b768efc711ed upstream. |
| |
| After 52d7523 (arm64: mm: allow the kernel to handle alignment faults on |
| user accesses) commit user-land accesses that produce unaligned exceptions |
| like in case of aarch32 ldm/stm/ldrd/strd instructions operating on |
| unaligned memory received by user-land as SIGSEGV. It is wrong, it should |
| be reported as SIGBUS as it was before 52d7523 commit. |
| |
| Changed do_bad_area function to take signal and code parameters out of esr |
| value using fault_info table, so in case of do_alignment_fault fault |
| user-land will receive SIGBUS. Wrapped access to fault_info table into |
| esr_to_fault_info function. |
| |
| Fixes: 52d7523 (arm64: mm: allow the kernel to handle alignment faults on user accesses) |
| Signed-off-by: Victor Kamensky <kamensky@cisco.com> |
| Signed-off-by: Will Deacon <will.deacon@arm.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/arm64/mm/fault.c | 42 ++++++++++++++++++++++++------------------ |
| 1 file changed, 24 insertions(+), 18 deletions(-) |
| |
| --- a/arch/arm64/mm/fault.c |
| +++ b/arch/arm64/mm/fault.c |
| @@ -41,7 +41,20 @@ |
| #include <asm/pgtable.h> |
| #include <asm/tlbflush.h> |
| |
| -static const char *fault_name(unsigned int esr); |
| +struct fault_info { |
| + int (*fn)(unsigned long addr, unsigned int esr, |
| + struct pt_regs *regs); |
| + int sig; |
| + int code; |
| + const char *name; |
| +}; |
| + |
| +static const struct fault_info fault_info[]; |
| + |
| +static inline const struct fault_info *esr_to_fault_info(unsigned int esr) |
| +{ |
| + return fault_info + (esr & 63); |
| +} |
| |
| #ifdef CONFIG_KPROBES |
| static inline int notify_page_fault(struct pt_regs *regs, unsigned int esr) |
| @@ -196,10 +209,12 @@ static void __do_user_fault(struct task_ |
| struct pt_regs *regs) |
| { |
| struct siginfo si; |
| + const struct fault_info *inf; |
| |
| if (unhandled_signal(tsk, sig) && show_unhandled_signals_ratelimited()) { |
| + inf = esr_to_fault_info(esr); |
| pr_info("%s[%d]: unhandled %s (%d) at 0x%08lx, esr 0x%03x\n", |
| - tsk->comm, task_pid_nr(tsk), fault_name(esr), sig, |
| + tsk->comm, task_pid_nr(tsk), inf->name, sig, |
| addr, esr); |
| show_pte(tsk->mm, addr); |
| show_regs(regs); |
| @@ -218,14 +233,16 @@ static void do_bad_area(unsigned long ad |
| { |
| struct task_struct *tsk = current; |
| struct mm_struct *mm = tsk->active_mm; |
| + const struct fault_info *inf; |
| |
| /* |
| * If we are in kernel mode at this point, we have no context to |
| * handle this fault with. |
| */ |
| - if (user_mode(regs)) |
| - __do_user_fault(tsk, addr, esr, SIGSEGV, SEGV_MAPERR, regs); |
| - else |
| + if (user_mode(regs)) { |
| + inf = esr_to_fault_info(esr); |
| + __do_user_fault(tsk, addr, esr, inf->sig, inf->code, regs); |
| + } else |
| __do_kernel_fault(mm, addr, esr, regs); |
| } |
| |
| @@ -481,12 +498,7 @@ static int do_bad(unsigned long addr, un |
| return 1; |
| } |
| |
| -static const struct fault_info { |
| - int (*fn)(unsigned long addr, unsigned int esr, struct pt_regs *regs); |
| - int sig; |
| - int code; |
| - const char *name; |
| -} fault_info[] = { |
| +static const struct fault_info fault_info[] = { |
| { do_bad, SIGBUS, 0, "ttbr address size fault" }, |
| { do_bad, SIGBUS, 0, "level 1 address size fault" }, |
| { do_bad, SIGBUS, 0, "level 2 address size fault" }, |
| @@ -553,19 +565,13 @@ static const struct fault_info { |
| { do_bad, SIGBUS, 0, "unknown 63" }, |
| }; |
| |
| -static const char *fault_name(unsigned int esr) |
| -{ |
| - const struct fault_info *inf = fault_info + (esr & 63); |
| - return inf->name; |
| -} |
| - |
| /* |
| * Dispatch a data abort to the relevant handler. |
| */ |
| asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr, |
| struct pt_regs *regs) |
| { |
| - const struct fault_info *inf = fault_info + (esr & 63); |
| + const struct fault_info *inf = esr_to_fault_info(esr); |
| struct siginfo info; |
| |
| if (!inf->fn(addr, esr, regs)) |