| From a1d863ac3e1085e1fea9caafd87252d08731de2e Mon Sep 17 00:00:00 2001 |
| From: Ilya Leoshkevich <iii@linux.ibm.com> |
| Date: Wed, 2 Oct 2019 13:29:57 +0200 |
| Subject: s390/unwind: fix mixing regs and sp |
| |
| From: Ilya Leoshkevich <iii@linux.ibm.com> |
| |
| commit a1d863ac3e1085e1fea9caafd87252d08731de2e upstream. |
| |
| unwind_for_each_frame stops after the first frame if regs->gprs[15] <= |
| sp. |
| |
| The reason is that in case regs are specified, the first frame should be |
| regs->psw.addr and the second frame should be sp->gprs[8]. However, |
| currently the second frame is regs->gprs[15], which confuses |
| outside_of_stack(). |
| |
| Fix by introducing a flag to distinguish this special case from |
| unwinding the interrupt handler, for which the current behavior is |
| appropriate. |
| |
| Fixes: 78c98f907413 ("s390/unwind: introduce stack unwind API") |
| Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> |
| Cc: stable@vger.kernel.org # v5.2+ |
| Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com> |
| Signed-off-by: Vasily Gorbik <gor@linux.ibm.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/s390/include/asm/unwind.h | 1 + |
| arch/s390/kernel/unwind_bc.c | 18 +++++++++++++----- |
| 2 files changed, 14 insertions(+), 5 deletions(-) |
| |
| --- a/arch/s390/include/asm/unwind.h |
| +++ b/arch/s390/include/asm/unwind.h |
| @@ -35,6 +35,7 @@ struct unwind_state { |
| struct task_struct *task; |
| struct pt_regs *regs; |
| unsigned long sp, ip; |
| + bool reuse_sp; |
| int graph_idx; |
| bool reliable; |
| bool error; |
| --- a/arch/s390/kernel/unwind_bc.c |
| +++ b/arch/s390/kernel/unwind_bc.c |
| @@ -46,10 +46,15 @@ bool unwind_next_frame(struct unwind_sta |
| |
| regs = state->regs; |
| if (unlikely(regs)) { |
| - sp = READ_ONCE_NOCHECK(regs->gprs[15]); |
| - if (unlikely(outside_of_stack(state, sp))) { |
| - if (!update_stack_info(state, sp)) |
| - goto out_err; |
| + if (state->reuse_sp) { |
| + sp = state->sp; |
| + state->reuse_sp = false; |
| + } else { |
| + sp = READ_ONCE_NOCHECK(regs->gprs[15]); |
| + if (unlikely(outside_of_stack(state, sp))) { |
| + if (!update_stack_info(state, sp)) |
| + goto out_err; |
| + } |
| } |
| sf = (struct stack_frame *) sp; |
| ip = READ_ONCE_NOCHECK(sf->gprs[8]); |
| @@ -107,9 +112,9 @@ void __unwind_start(struct unwind_state |
| { |
| struct stack_info *info = &state->stack_info; |
| unsigned long *mask = &state->stack_mask; |
| + bool reliable, reuse_sp; |
| struct stack_frame *sf; |
| unsigned long ip; |
| - bool reliable; |
| |
| memset(state, 0, sizeof(*state)); |
| state->task = task; |
| @@ -134,10 +139,12 @@ void __unwind_start(struct unwind_state |
| if (regs) { |
| ip = READ_ONCE_NOCHECK(regs->psw.addr); |
| reliable = true; |
| + reuse_sp = true; |
| } else { |
| sf = (struct stack_frame *) sp; |
| ip = READ_ONCE_NOCHECK(sf->gprs[8]); |
| reliable = false; |
| + reuse_sp = false; |
| } |
| |
| #ifdef CONFIG_FUNCTION_GRAPH_TRACER |
| @@ -151,5 +158,6 @@ void __unwind_start(struct unwind_state |
| state->sp = sp; |
| state->ip = ip; |
| state->reliable = reliable; |
| + state->reuse_sp = reuse_sp; |
| } |
| EXPORT_SYMBOL_GPL(__unwind_start); |