To enable support for SFrame V3 flexible FDEs with a subsequent patch, add support for the following flexible Canonical Frame Address (CFA) recovery rules:
CFA = SP + offset CFA = FP + offset CFA = register + offset CFA = *(register + offset) Note that CFA recovery rules that use arbitrary register contents are only valid when in the topmost frame, as their contents are otherwise unknown. Cc: Steven Rostedt <[email protected]> Cc: Josh Poimboeuf <[email protected]> Cc: Masami Hiramatsu <[email protected]> Cc: Mathieu Desnoyers <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: Jiri Olsa <[email protected]> Cc: Arnaldo Carvalho de Melo <[email protected]> Cc: Namhyung Kim <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: Andrii Nakryiko <[email protected]> Cc: Indu Bhagat <[email protected]> Cc: "Jose E. Marchesi" <[email protected]> Cc: Beau Belgrave <[email protected]> Cc: Jens Remus <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Andrew Morton <[email protected]> Cc: Florian Weimer <[email protected]> Cc: Sam James <[email protected]> Cc: Kees Cook <[email protected]> Cc: "Carlos O'Donell" <[email protected]> Signed-off-by: Jens Remus <[email protected]> --- Notes (jremus): Changes in v13: - New patch. arch/x86/include/asm/unwind_user.h | 12 ++++++++---- include/linux/unwind_user_types.h | 18 ++++++++++++++++-- kernel/unwind/sframe.c | 15 +++++++++++++-- kernel/unwind/user.c | 22 ++++++++++++++++++---- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index 9c3417be4283..f38f7c5ff1de 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -20,7 +20,10 @@ static inline int unwind_user_word_size(struct pt_regs *regs) #ifdef CONFIG_HAVE_UNWIND_USER_FP #define ARCH_INIT_USER_FP_FRAME(ws) \ - .cfa_off = 2*(ws), \ + .cfa = { \ + .rule = UNWIND_USER_CFA_RULE_FP_OFFSET,\ + .offset = 2*(ws), \ + }, \ .ra = { \ .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\ .offset = -1*(ws), \ @@ -29,11 +32,13 @@ static inline int unwind_user_word_size(struct pt_regs *regs) .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\ .offset = -2*(ws), \ }, \ - .use_fp = true, \ .outermost = false, #define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \ - .cfa_off = 1*(ws), \ + .cfa = { \ + .rule = UNWIND_USER_CFA_RULE_SP_OFFSET,\ + .offset = 1*(ws), \ + }, \ .ra = { \ .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\ .offset = -1*(ws), \ @@ -41,7 +46,6 @@ static inline int unwind_user_word_size(struct pt_regs *regs) .fp = { \ .rule = UNWIND_USER_RULE_RETAIN,\ }, \ - .use_fp = false, \ .outermost = false, static inline bool unwind_user_at_function_start(struct pt_regs *regs) diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h index 0d02714a1b5d..059e5c76f2f3 100644 --- a/include/linux/unwind_user_types.h +++ b/include/linux/unwind_user_types.h @@ -29,6 +29,21 @@ struct unwind_stacktrace { #define UNWIND_USER_RULE_DEREF BIT(31) +enum unwind_user_cfa_rule { + UNWIND_USER_CFA_RULE_SP_OFFSET, /* CFA = SP + offset */ + UNWIND_USER_CFA_RULE_FP_OFFSET, /* CFA = FP + offset */ + UNWIND_USER_CFA_RULE_REG_OFFSET, /* CFA = reg + offset */ + /* DEREF variants */ + UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF = /* CFA = *(reg + offset) */ + UNWIND_USER_CFA_RULE_REG_OFFSET | UNWIND_USER_RULE_DEREF, +}; + +struct unwind_user_cfa_rule_data { + enum unwind_user_cfa_rule rule; + s32 offset; + unsigned int regnum; +}; + enum unwind_user_rule { UNWIND_USER_RULE_RETAIN, /* entity = entity */ UNWIND_USER_RULE_CFA_OFFSET, /* entity = CFA + offset */ @@ -47,10 +62,9 @@ struct unwind_user_rule_data { }; struct unwind_user_frame { - s32 cfa_off; + struct unwind_user_cfa_rule_data cfa; struct unwind_user_rule_data ra; struct unwind_user_rule_data fp; - bool use_fp; bool outermost; }; diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index b5301fa9dbc8..4dfc8cf2075e 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -271,6 +271,18 @@ static __always_inline int __read_fre(struct sframe_section *sec, return -EFAULT; } +static __always_inline void +sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data, + unsigned char fre_info, + s32 offset) +{ + if (SFRAME_V3_FRE_CFA_BASE_REG_ID(fre_info) == SFRAME_BASE_REG_FP) + cfa_rule_data->rule = UNWIND_USER_CFA_RULE_FP_OFFSET; + else + cfa_rule_data->rule = UNWIND_USER_CFA_RULE_SP_OFFSET; + cfa_rule_data->offset = offset; +} + static __always_inline void sframe_init_rule_data(struct unwind_user_rule_data *rule_data, s32 offset) @@ -332,10 +344,9 @@ static __always_inline int __find_fre(struct sframe_section *sec, return -EINVAL; fre = prev_fre; - frame->cfa_off = fre->cfa_off; + sframe_init_cfa_rule_data(&frame->cfa, fre->info, fre->cfa_off); sframe_init_rule_data(&frame->ra, fre->ra_off); sframe_init_rule_data(&frame->fp, fre->fp_off); - frame->use_fp = SFRAME_V3_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP; frame->outermost = SFRAME_V3_FRE_RA_UNDEFINED_P(fre->info); return 0; diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index 0405922c5c0d..eb7d9489f671 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -39,14 +39,28 @@ static int unwind_user_next_common(struct unwind_user_state *state, } /* Get the Canonical Frame Address (CFA) */ - if (frame->use_fp) { + switch (frame->cfa.rule) { + case UNWIND_USER_CFA_RULE_SP_OFFSET: + cfa = state->sp; + break; + case UNWIND_USER_CFA_RULE_FP_OFFSET: if (state->fp < state->sp) return -EINVAL; cfa = state->fp; - } else { - cfa = state->sp; + break; + case UNWIND_USER_CFA_RULE_REG_OFFSET: + case UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF: + if (!state->topmost || unwind_user_get_reg(&cfa, frame->cfa.regnum)) + return -EINVAL; + break; + default: + WARN_ON_ONCE(1); + return -EINVAL; } - cfa += frame->cfa_off; + cfa += frame->cfa.offset; + if (frame->cfa.rule & UNWIND_USER_RULE_DEREF && + get_user_word(&cfa, cfa, 0, state->ws)) + return -EINVAL; /* * Make sure that stack is not going in wrong direction. Allow SP -- 2.51.0
