__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


Reply via email to