Enable architectures to handle DWARF expressions in DWARF CFI
instructions DW_CFA_def_cfa_expression, DW_CFA_expression, and
DW_CFA_val_expression.  Limit the maximum expression length to a
reasonable size, while enabling architectures to override the
limit.

Architectures are supposed to only handle specific known expressions
or expression patterns, not to implement a stack-based expression
evaluation machinery.

Signed-off-by: Jens Remus <[email protected]>
---
 include/asm-generic/unwind_user_eh_frame.h | 41 +++++++++++++
 include/linux/unwind_user_eh_frame_types.h | 37 ++++++++++++
 kernel/unwind/eh_frame.c                   | 70 +++++++++++-----------
 3 files changed, 113 insertions(+), 35 deletions(-)
 create mode 100644 include/linux/unwind_user_eh_frame_types.h

diff --git a/include/asm-generic/unwind_user_eh_frame.h 
b/include/asm-generic/unwind_user_eh_frame.h
index e6d207597206..da9bc52a645a 100644
--- a/include/asm-generic/unwind_user_eh_frame.h
+++ b/include/asm-generic/unwind_user_eh_frame.h
@@ -2,6 +2,8 @@
 #ifndef _ASM_GENERIC_UNWIND_USER_EH_FRAME_H
 #define _ASM_GENERIC_UNWIND_USER_EH_FRAME_H
 
+#include <linux/unwind_user_eh_frame_types.h>
+
 #ifndef EH_FRAME_MAX_CIE_LENGTH
 #define EH_FRAME_MAX_CIE_LENGTH 128
 #endif
@@ -10,6 +12,10 @@
 #define EH_FRAME_MAX_AUGSTR_LENGTH 16
 #endif
 
+#ifndef EH_FRAME_MAX_EXPRESSION_LENGTH
+#define EH_FRAME_MAX_EXPRESSION_LENGTH 32
+#endif
+
 #ifndef EH_FRAME_MAX_STATE_STACK
 #define EH_FRAME_MAX_STATE_STACK 8
 #endif
@@ -39,5 +45,40 @@ static inline bool eh_frame_reject_sp_rule(void)
 #define eh_frame_reject_sp_rule eh_frame_reject_sp_rule
 #endif
 
+#ifndef eh_frame_do_def_cfa_expression
+static inline int eh_frame_do_def_cfa_expression(const char *expr,
+                                                int size,
+                                                unsigned long ip,
+                                                struct eh_frame_reg_state 
*reg_state)
+{
+       return -EOPNOTSUPP;
+}
+#define eh_frame_do_def_cfa_expression eh_frame_do_def_cfa_expression
+#endif
+
+#ifndef eh_frame_do_expression
+static inline int eh_frame_do_expression(unsigned int reg,
+                                        const char *expr,
+                                        int size,
+                                        unsigned long ip,
+                                        struct eh_frame_reg_state *reg_state)
+{
+       return -EOPNOTSUPP;
+}
+#define eh_frame_do_expression eh_frame_do_expression
+#endif
+
+#ifndef eh_frame_do_val_expression
+static inline int eh_frame_do_val_expression(unsigned int reg,
+                                            const char *expr,
+                                            int size,
+                                            unsigned long ip,
+                                            struct eh_frame_reg_state 
*reg_state)
+{
+       return -EOPNOTSUPP;
+}
+#define eh_frame_do_val_expression eh_frame_do_val_expression
+#endif
+
 #endif /* _ASM_GENERIC_UNWIND_USER_EH_FRAME_H */
 
diff --git a/include/linux/unwind_user_eh_frame_types.h 
b/include/linux/unwind_user_eh_frame_types.h
new file mode 100644
index 000000000000..e9f9d1abb76f
--- /dev/null
+++ b/include/linux/unwind_user_eh_frame_types.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_UNWIND_USER_EH_FRAME_TYPES_H
+#define _LINUX_UNWIND_USER_EH_FRAME_TYPES_H
+
+enum eh_frame_cfa_rule {
+       CFA_UNDEFINED,          /* unrecoverable */
+       CFA_REG_OFFSET,         /* CFA = reg + offset */
+};
+
+enum eh_frame_reg_rule {
+       REG_UNDEFINED_IMPLICIT, /* reg = reg */
+       REG_UNDEFINED_EXPLICIT, /* unrecoverable; RA: outermost frame */
+       REG_SAME_VALUE,         /* reg = reg; TODO: reset to CIE initial CFI */
+       REG_OFFSET,             /* reg = *(CFA + offset) */
+       REG_VAL_OFFSET,         /* reg = CFA + offset */
+       REG_REGISTER,           /* reg = other_reg */
+};
+
+enum eh_frame_reg_index {
+       FP_IDX,                 /* frame pointer (FP) */
+       RA_IDX,                 /* return address (RA) */
+       NR_REGS
+};
+
+struct eh_frame_reg_state {
+       /* CFA recovery rule */
+       enum eh_frame_cfa_rule cfa_rule;
+       unsigned long cfa_regnum;
+       long cfa_offset;
+
+       /* FP and RA recovery rules (SP uses implicit recovery) */
+       enum eh_frame_reg_rule reg_rule[NR_REGS];
+       unsigned long reg_regnum[NR_REGS];
+       long reg_offset[NR_REGS];
+};
+
+#endif /* _LINUX_UNWIND_USER_EH_FRAME_TYPES_H */
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
index 64176242b7d8..f7f1234b0437 100644
--- a/kernel/unwind/eh_frame.c
+++ b/kernel/unwind/eh_frame.c
@@ -12,45 +12,14 @@
 #include <linux/mm.h>
 #include <linux/string_helpers.h>
 #include <linux/eh_frame.h>
+#include <linux/types.h>
 #include <linux/unwind_user_types.h>
+#include <linux/unwind_user_eh_frame_types.h>
 #include <asm/unwind_user_eh_frame.h>
 
 #include "eh_frame.h"
 #include "eh_frame_debug.h"
 
-/* Register state for CFI interpreter */
-enum eh_frame_cfa_rule {
-       CFA_UNDEFINED,          /* unrecoverable */
-       CFA_REG_OFFSET,         /* CFA = reg + offset */
-};
-
-enum eh_frame_reg_rule {
-       REG_UNDEFINED_IMPLICIT, /* reg = reg */
-       REG_UNDEFINED_EXPLICIT, /* unrecoverable; RA: outermost frame */
-       REG_SAME_VALUE,         /* reg = reg; TODO: reset to CIE initial CFI */
-       REG_OFFSET,             /* reg = *(CFA + offset) */
-       REG_VAL_OFFSET,         /* reg = CFA + offset */
-       REG_REGISTER,           /* reg = other_reg */
-};
-
-enum eh_frame_reg_index {
-       FP_IDX,                 /* frame pointer (FP) */
-       RA_IDX,                 /* return address (RA) */
-       NR_REGS
-};
-
-struct eh_frame_reg_state {
-       /* CFA recovery rule */
-       enum eh_frame_cfa_rule cfa_rule;
-       unsigned long cfa_regnum;
-       long cfa_offset;
-
-       /* FP and RA recovery rules (SP uses implicit recovery) */
-       enum eh_frame_reg_rule reg_rule[NR_REGS];
-       unsigned long reg_regnum[NR_REGS];
-       long reg_offset[NR_REGS];
-};
-
 struct eh_frame_cfi_context {
        struct eh_frame_reg_state state;
        struct eh_frame_reg_state stack[EH_FRAME_MAX_STATE_STACK];
@@ -839,6 +808,27 @@ static __always_inline int __do_cfi_insn(struct 
eh_frame_section *sec,
                        break;
                }
 
+               case DW_CFA_def_cfa_expression: {
+                       unsigned long expr_len;
+                       char expr[EH_FRAME_MAX_EXPRESSION_LENGTH];
+
+                       ret = read_uleb128(&cur, end, &expr_len);
+                       if (ret)
+                               return ret;
+
+                       if (cur + expr_len > end)
+                               return -EINVAL;
+
+                       if (expr_len > sizeof(expr))
+                               return -EOPNOTSUPP;
+                       unsafe_copy_from_user(&expr, (void __user *)cur, 
expr_len, Efault);
+                       ret = eh_frame_do_def_cfa_expression(expr, expr_len, 
target_ip, &ctx->state);
+                       if (ret)
+                               return ret;
+                       cur += expr_len;
+                       break;
+               }
+
                case DW_CFA_undefined: {
                        unsigned long reg;
                        int idx;
@@ -1005,9 +995,19 @@ static __always_inline int __do_cfi_insn(struct 
eh_frame_section *sec,
                        if (cur + expr_len > end)
                                return -EINVAL;
 
-                       if (reg == EH_FRAME_REG_SP || reg == EH_FRAME_REG_FP || 
reg == EH_FRAME_REG_RA)
-                               return -EOPNOTSUPP;
+                       if (reg == EH_FRAME_REG_SP || reg == EH_FRAME_REG_FP || 
reg == EH_FRAME_REG_RA) {
+                               char expr[EH_FRAME_MAX_EXPRESSION_LENGTH];
 
+                               if (expr_len > sizeof(expr))
+                                       return -EOPNOTSUPP;
+                               unsafe_copy_from_user(&expr, (void __user 
*)cur, expr_len, Efault);
+                               if (opcode == DW_CFA_expression)
+                                       ret = eh_frame_do_expression(reg, expr, 
expr_len, target_ip, &ctx->state);
+                               else
+                                       ret = eh_frame_do_val_expression(reg, 
expr, expr_len, target_ip, &ctx->state);
+                               if (ret)
+                                       return ret;
+                       }
                        cur += expr_len;
                        break;
                }
-- 
2.53.0


Reply via email to