diff --git a/arch/Kconfig b/arch/Kconfig
index 86eec85cb898..367eaf7e62e0 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -450,6 +450,11 @@ config HAVE_UNWIND_USER_SFRAME
bool
select UNWIND_USER
+config HAVE_USER_RA_REG
+ bool
+ help
+ The arch passes the return address (RA) in user space in a register.
+
config SFRAME_VALIDATION
bool "Enable .sframe section debugging"
depends on HAVE_UNWIND_USER_SFRAME
diff --git a/include/linux/ptrace.h b/include/linux/ptrace.h
index 90507d4afcd6..a245c8586673 100644
--- a/include/linux/ptrace.h
+++ b/include/linux/ptrace.h
@@ -397,6 +397,14 @@ static inline void user_single_step_report(struct pt_regs *regs)
#define exception_ip(x) instruction_pointer(x)
#endif
+#ifndef user_return_address
+static inline unsigned long user_return_address(const struct pt_regs *regs)
+{
+ WARN_ON_ONCE(1);
+ return 0;
+}
+#endif
+
extern int task_current_syscall(struct task_struct *target, struct syscall_info *info);
extern void sigaction_compat_abi(struct k_sigaction *act, struct k_sigaction *oact);diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index b79c5ec09229..e3c6414f1a17 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -33,7 +33,7 @@ extern void sframe_free_mm(struct mm_struct *mm);
extern int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end);
extern int sframe_remove_section(unsigned long sframe_addr);
-extern int sframe_find(unsigned long ip, struct unwind_user_frame *frame);
+extern int sframe_find(unsigned long ip, struct unwind_user_frame *frame, bool topmost);
static inline bool current_has_sframe(void)
{@@ -52,7 +52,7 @@ static inline int sframe_add_section(unsigned long sframe_start, unsigned long s
return -ENOSYS;
}
static inline int sframe_remove_section(unsigned long sframe_addr) { return -ENOSYS; }
-static inline int sframe_find(unsigned long ip, struct unwind_user_frame *frame) { return -ENOSYS; }
+static inline int sframe_find(unsigned long ip, struct unwind_user_frame *frame, bool topmost) { return -ENOSYS; }
static inline bool current_has_sframe(void) { return false; }
#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h
index 8050a3237a03..adef01698bb3 100644
--- a/include/linux/unwind_user_types.h
+++ b/include/linux/unwind_user_types.h
@@ -35,6 +35,7 @@ struct unwind_user_state {
unsigned long fp;
struct arch_unwind_user_state arch;
enum unwind_user_type type;
+ bool topmost;
bool done;
};
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index acbf791e713b..5bfaf06e6cd2 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -222,12 +222,8 @@ static __always_inline int __read_fre(struct sframe_section *sec,
offset_count--;
ra_off = sec->ra_off;
- if (!ra_off) {
- if (!offset_count--) {
- dbg_sec_uaccess("zero offset_count, can't find ra_off\n");
- return -EFAULT;
- }
-
+ if (!ra_off && offset_count) {
+ offset_count--;
UNSAFE_GET_USER_INC(ra_off, cur, offset_size, Efault);
}
@@ -257,7 +253,8 @@ static __always_inline int __read_fre(struct sframe_section *sec,
static __always_inline int __find_fre(struct sframe_section *sec,
struct sframe_fde *fde, unsigned long ip,
- struct unwind_user_frame *frame)
+ struct unwind_user_frame *frame,
+ bool topmost)
{
unsigned char fde_type = SFRAME_FUNC_FDE_TYPE(fde->info);
struct sframe_fre *fre, *prev_fre = NULL;@@ -310,6 +307,12 @@ static __always_inline int __find_fre(struct sframe_section *sec,
return -EINVAL;
fre = prev_fre;
+ if ((!IS_ENABLED(CONFIG_HAVE_USER_RA_REG) || !topmost) && !fre->ra_off) {
+ dbg_sec_uaccess("fde addr 0x%x: zero ra_off\n",
+ fde->start_addr);
+ return -EINVAL;
+ }
+
frame->cfa_off = fre->cfa_off;
frame->ra_off = fre->ra_off;
frame->fp_off = fre->fp_off;@@ -319,7 +322,7 @@ static __always_inline int __find_fre(struct sframe_section *sec,
return 0;
}
-int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
+int sframe_find(unsigned long ip, struct unwind_user_frame *frame, bool topmost)
{
struct mm_struct *mm = current->mm;
struct sframe_section *sec;@@ -343,7 +346,7 @@ int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
if (ret)
goto end;
- ret = __find_fre(sec, &fde, ip, frame);
+ ret = __find_fre(sec, &fde, ip, frame, topmost);
end:
user_read_access_end();
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index 45c8c6932ba6..03a6da36192f 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -3,6 +3,7 @@
* Generic interfaces for unwinding user space
*/
#include <linux/kernel.h>
+#include <linux/ptrace.h>
#include <linux/sched.h>
#include <linux/sched/task_stack.h>
#include <linux/unwind_user.h>
@@ -53,6 +54,7 @@ static int unwind_user_next(struct unwind_user_state *state)
struct unwind_user_frame *frame;
struct unwind_user_frame _frame;
unsigned long cfa = 0, sp, fp, ra = 0;
+ bool topmost = state->topmost;
unsigned int shift;
if (state->done)
@@ -63,7 +65,7 @@ static int unwind_user_next(struct unwind_user_state *state)
} else if (sframe_state(state)) {
/* sframe expects the frame to be local storage */
frame = &_frame;
- if (sframe_find(state->ip, frame)) {
+ if (sframe_find(state->ip, frame, topmost)) {
if (!IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP))
goto done;
frame = &fp_frame;@@ -86,18 +88,28 @@ static int unwind_user_next(struct unwind_user_state *state)
/* Get the Stack Pointer (SP) */
sp = cfa + frame->sp_val_off;
- /* Make sure that stack is not going in wrong direction */
- if (sp <= state->sp)
+ /*
+ * Make sure that stack is not going in wrong direction. Allow SP
+ * to be unchanged for the topmost frame, by subtracting topmost,
+ * which is either 0 or 1.
+ */
+ if (sp <= state->sp - topmost)
goto done;
- /* Make sure that the address is word aligned */
- shift = sizeof(long) == 4 || compat_fp_state(state) ? 2 : 3;
- if ((cfa + frame->ra_off) & ((1 << shift) - 1))
- goto done;
/* Get the Return Address (RA) */
- if (unwind_get_user_long(ra, cfa + frame->ra_off, state))
- goto done;
+ if (frame->ra_off) {
+ /* Make sure that the address is word aligned */
+ shift = sizeof(long) == 4 || compat_fp_state(state) ? 2 : 3;
+ if ((cfa + frame->ra_off) & ((1 << shift) - 1))
+ goto done;
+ if (unwind_get_user_long(ra, cfa + frame->ra_off, state))
+ goto done;
+ } else {
+ if (!IS_ENABLED(CONFIG_HAVE_USER_RA_REG) || !topmost)
+ goto done;
+ ra = user_return_address(task_pt_regs(current));
+ }
/* Get the Frame Pointer (FP) */
if (frame->fp_off && unwind_get_user_long(fp, cfa + frame->fp_off, state))@@ -110,6 +122,7 @@ static int unwind_user_next(struct unwind_user_state *state)
arch_unwind_user_next(state);
+ state->topmost = false;
return 0;
done:
@@ -140,6 +153,7 @@ static int unwind_user_start(struct unwind_user_state *state)
state->ip = instruction_pointer(regs);
state->sp = user_stack_pointer(regs);
state->fp = frame_pointer(regs);
+ state->topmost = true;
arch_unwind_user_init(state, regs);
--
2.48.1