__find_fre() performs linear search for a matching SFrame FRE for a given IP. For that purpose it uses __read_fre(), which reads the whole FRE. That is the variable-size FRE structure as well as the trailing variable-length array of variable-size data words. For the search logic to skip over the FRE it would be sufficient to read the variable-size FRE structure only, which includes the count and size of data words.
Add fields to struct sframe_fre_internal to store the FRE data word's address, count, and size. Change __read_fre() to read the variable- size FRE structure only and populate those new fields. Change __read_fre_datawords() to use those new fields. Change __find_fre() to use __read_fre_datawords() to read the FRE data words only after a matching FRE has been found. Introduce safe_read_fre_datawords() and use it in sframe_validate_section() to validate that the FRE data words. 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. kernel/unwind/sframe.c | 91 +++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c index ebf2a2905c5c..f24997e84e05 100644 --- a/kernel/unwind/sframe.c +++ b/kernel/unwind/sframe.c @@ -39,6 +39,9 @@ struct sframe_fre_internal { u32 fp_ctl; s32 fp_off; u8 info; + unsigned long dw_addr; + unsigned char dw_count; + unsigned char dw_size; }; DEFINE_STATIC_SRCU(sframe_srcu); @@ -196,11 +199,11 @@ static __always_inline int __find_fde(struct sframe_section *sec, static __always_inline int __read_regular_fre_datawords(struct sframe_section *sec, struct sframe_fde_internal *fde, - unsigned long cur, - unsigned char dataword_count, - unsigned char dataword_size, struct sframe_fre_internal *fre) { + unsigned char dataword_count = fre->dw_count; + unsigned char dataword_size = fre->dw_size; + unsigned long cur = fre->dw_addr; s32 cfa_off, ra_off, fp_off; unsigned int cfa_regnum; @@ -242,11 +245,11 @@ __read_regular_fre_datawords(struct sframe_section *sec, static __always_inline int __read_flex_fde_fre_datawords(struct sframe_section *sec, struct sframe_fde_internal *fde, - unsigned long cur, - unsigned char dataword_count, - unsigned char dataword_size, struct sframe_fre_internal *fre) { + unsigned char dataword_count = fre->dw_count; + unsigned char dataword_size = fre->dw_size; + unsigned long cur = fre->dw_addr; u32 cfa_ctl, ra_ctl, fp_ctl; s32 cfa_off, ra_off, fp_off; @@ -303,24 +306,28 @@ __read_flex_fde_fre_datawords(struct sframe_section *sec, static __always_inline int __read_fre_datawords(struct sframe_section *sec, struct sframe_fde_internal *fde, - unsigned long cur, - unsigned char dataword_count, - unsigned char dataword_size, struct sframe_fre_internal *fre) { unsigned char fde_type = SFRAME_V3_FDE_TYPE(fde->info2); + unsigned char dataword_count = fre->dw_count; + + if (!dataword_count) { + /* A FRE without data words indicates an outermost frame. */ + fre->cfa_ctl = 0; + fre->cfa_off = 0; + fre->ra_ctl = 0; + fre->ra_off = 0; + fre->fp_ctl = 0; + fre->fp_off = 0; + + return 0; + } switch (fde_type) { case SFRAME_FDE_TYPE_REGULAR: - return __read_regular_fre_datawords(sec, fde, cur, - dataword_count, - dataword_size, - fre); + return __read_regular_fre_datawords(sec, fde, fre); case SFRAME_FDE_TYPE_FLEXIBLE: - return __read_flex_fde_fre_datawords(sec, fde, cur, - dataword_count, - dataword_size, - fre); + return __read_flex_fde_fre_datawords(sec, fde, fre); default: return -EFAULT; } @@ -362,23 +369,11 @@ static __always_inline int __read_fre(struct sframe_section *sec, fre->size = addr_size + 1 + (dataword_count * dataword_size); fre->ip_off = ip_off; fre->info = info; + fre->dw_addr = cur; + fre->dw_count = dataword_count; + fre->dw_size = dataword_size; - if (!dataword_count) { - /* - * A FRE without data words indicates RA undefined / - * outermost frame. - */ - fre->cfa_ctl = 0; - fre->cfa_off = 0; - fre->ra_ctl = 0; - fre->ra_off = 0; - fre->fp_ctl = 0; - fre->fp_off = 0; - - return 0; - } - - return __read_fre_datawords(sec, fde, cur, dataword_count, dataword_size, fre); + return 0; Efault: return -EFAULT; @@ -455,6 +450,7 @@ static __always_inline int __find_fre(struct sframe_section *sec, bool which = false; unsigned int i; u32 ip_off; + int ret; ip_off = ip - fde->func_addr; @@ -492,6 +488,10 @@ static __always_inline int __find_fre(struct sframe_section *sec, return -EINVAL; fre = prev_fre; + ret = __read_fre_datawords(sec, fde, fre); + if (ret) + return ret; + if (sframe_init_cfa_rule_data(&frame->cfa, fre->cfa_ctl, fre->cfa_off)) return -EINVAL; sframe_init_rule_data(&frame->ra, fre->ra_ctl, fre->ra_off); @@ -567,6 +567,20 @@ static int safe_read_fre(struct sframe_section *sec, return ret; } +static int safe_read_fre_datawords(struct sframe_section *sec, + struct sframe_fde_internal *fde, + struct sframe_fre_internal *fre) +{ + int ret; + + if (!user_read_access_begin((void __user *)sec->sframe_start, + sec->sframe_end - sec->sframe_start)) + return -EFAULT; + ret = __read_fre_datawords(sec, fde, fre); + user_read_access_end(); + return ret; +} + static int sframe_validate_section(struct sframe_section *sec) { unsigned long prev_ip = 0; @@ -610,6 +624,17 @@ static int sframe_validate_section(struct sframe_section *sec) fde.rep_size); return ret; } + ret = safe_read_fre_datawords(sec, &fde, fre); + if (ret) { + dbg_sec("fde %u: __read_fre_datawords(%u) failed\n", i, j); + dbg_sec("FDE: func_addr:%#lx func_size:%#x fda_off:%#x fres_off:%#x fres_num:%d info:%u info2:%u rep_size:%u\n", + fde.func_addr, fde.func_size, + fde.fda_off, + fde.fres_off, fde.fres_num, + fde.info, fde.info2, + fde.rep_size); + return ret; + } fre_addr += fre->size; -- 2.51.0
