[PATCH v13 16/18] unwind_user/sframe: Separate reading of FRE from reading of FRE data words
From: Jens Remus <hidden>
Date: 2026-01-27 15:07:10
Also in:
bpf, linux-mm, lkml
Subsystem:
the rest, userspace stack unwinding · Maintainers:
Linus Torvalds, Josh Poimboeuf, Steven Rostedt
__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 <rostedt@kernel.org>
Cc: Josh Poimboeuf <jpoimboe@kernel.org>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Thomas Gleixner <redacted>
Cc: Andrii Nakryiko <andrii@kernel.org>
Cc: Indu Bhagat <redacted>
Cc: "Jose E. Marchesi" <redacted>
Cc: Beau Belgrave <redacted>
Cc: Jens Remus <redacted>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Florian Weimer <redacted>
Cc: Sam James <redacted>
Cc: Kees Cook <kees@kernel.org>
Cc: "Carlos O'Donell" <redacted>
Signed-off-by: Jens Remus <redacted>
---
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