Add a debug feature to validate all .eh_frame[_hdr] sections when first
loading the file rather than on demand.

Signed-off-by: Jens Remus <[email protected]>
---

Notes (jremus):
    FIXME: dbg*() with UACCESS enabled.
    
    Changes in RFC v2:
    - Fix compile error. (Sashiko AI)
    - Correct loop counter type to prevent infinite loop. (Sashiko AI)

 arch/Kconfig                   | 22 +++++++++
 kernel/unwind/eh_frame.c       | 90 ++++++++++++++++++++++++++++++++++
 kernel/unwind/eh_frame_debug.h |  4 ++
 3 files changed, 116 insertions(+)

diff --git a/arch/Kconfig b/arch/Kconfig
index ea969811f798..c06e5010a88a 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -490,6 +490,28 @@ config HAVE_UNWIND_USER_EH_FRAME
        bool
        select UNWIND_USER
 
+config EH_FRAME_VALIDATION
+       bool "Enable .eh_frame[_hdr] section debugging"
+       depends on HAVE_UNWIND_USER_EH_FRAME
+       depends on DYNAMIC_DEBUG
+       help
+         When adding an .eh_frame_hdr section for a test, validate the
+         entire section and its referenced entire .eh_frame section
+         immediately rather than on demand.
+
+         This is a debug feature which is helpful for rooting out
+         .eh_frame[_hdr] section issues.  If the .eh_frame[_hdr]
+         section is corrupt, it will fail to load immediately, with
+         more information provided in dynamic printks.
+
+         This has a significant page cache footprint due to its reading
+         of the entire .eh_frame[_hdr] sections for every loaded executable
+         and shared library.  Also, it's done for all processes, even those
+         which don't get stack traced by the kernel.  Not recommended for
+         general use.
+
+         If unsure, say N.
+
 config HAVE_UNWIND_USER_FP
        bool
        select UNWIND_USER
diff --git a/kernel/unwind/eh_frame.c b/kernel/unwind/eh_frame.c
index 96237a96d263..b0be56e31508 100644
--- a/kernel/unwind/eh_frame.c
+++ b/kernel/unwind/eh_frame.c
@@ -1216,6 +1216,92 @@ int eh_frame_find(unsigned long ip, struct 
unwind_user_frame *frame)
        return ret;
 }
 
+#ifdef CONFIG_EH_FRAME_VALIDATION
+
+static int eh_frame_validate_section(struct eh_frame_section *sec)
+{
+       void __user *table_start_ptr;
+       unsigned long table_size;
+       u8 table_enc;
+       int entry_size;
+       unsigned long prev_func_addr;
+       unsigned long i;
+
+       if (!sec->fde_count) {
+               dbg_sec(".eh_frame_hdr: invalid FDE count\n");
+               return -EINVAL;
+       }
+
+       table_enc = sec->binary_search_table_enc;
+       entry_size = 2 * encoded_pointer_size(table_enc);
+       if (!entry_size) {
+               dbg_sec(".eh_frame_hdr: invalid binary search table entry 
size\n");
+               return -EINVAL;
+       }
+       table_start_ptr = (void __user *)sec->binary_search_table_start;
+       table_size = sec->binary_search_table_end - 
sec->binary_search_table_start;
+
+       for (i = 0; i < sec->fde_count; i++) {
+               struct eh_frame_fde fde;
+               unsigned long cur;
+               unsigned long func_addr, fde_addr;
+               int ret;
+
+               cur = sec->binary_search_table_start + i * entry_size;
+
+               scoped_user_read_access_size(table_start_ptr, table_size, 
Efault) {
+                       /* Read function start address from table */
+                       ret = read_encoded_pointer(sec, &cur,
+                                                  sec->binary_search_table_end,
+                                                  table_enc, &func_addr);
+                       if (ret) {
+                               dbg_sec_ehfh(cur, "table[%lu]: failed to read 
function start address\n", i);
+                               return ret;
+                       }
+                       if (i && func_addr <= prev_func_addr) {
+                               dbg_sec(".eh_frame_hdr: table[%lu]: not 
sorted\n", i);
+                               return -EINVAL;
+                       }
+                       prev_func_addr = func_addr;
+
+                       /* Read FDE address from table */
+                       ret = read_encoded_pointer(sec, &cur,
+                                                  sec->binary_search_table_end,
+                                                  table_enc, &fde_addr);
+                       if (ret) {
+                               dbg_sec_ehfh(cur, "table[%lu]: failed to read 
FDE pointer\n", i);
+                               return ret;
+                       }
+                       if (fde_addr < sec->eh_frame_start) {
+                               dbg_sec(".eh_frame_hdr: table[%lu]: invalid FDE 
address\n", i);
+                               return -EINVAL;
+                       }
+               }
+
+               ret = __read_fde(sec, fde_addr, &fde);
+               if (ret) {
+                       dbg_sec(".eh_frame_hdr: table[%lu]: failed to read FDE 
at .eh_frame+%#lx\n",
+                               i, fde_addr - sec->eh_frame_start);
+                       return ret;
+               }
+               if (func_addr != fde.func_addr) {
+                       dbg_sec(".eh_frame_hdr: table[%lu]: function start 
address mismatch\n", i);
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+
+Efault:
+       return -EFAULT;
+}
+
+#else /* !CONFIG_EH_FRAME_VALIDATION */
+
+static int eh_frame_validate_section(struct eh_frame_section *sec) { return 0; 
}
+
+#endif /* !CONFIG_EH_FRAME_VALIDATION */
+
 static void free_section(struct eh_frame_section *sec)
 {
        dbg_free(sec);
@@ -1351,6 +1437,10 @@ int eh_frame_add_section(unsigned long 
eh_frame_hdr_start,
        if (ret)
                goto err_free;
 
+       ret = eh_frame_validate_section(sec);
+       if (ret)
+               goto err_free;
+
        ret = mtree_insert_range(eh_frame_mt, sec->text_start, sec->text_end - 
1,
                                 sec, GFP_KERNEL_ACCOUNT);
        if (ret) {
diff --git a/kernel/unwind/eh_frame_debug.h b/kernel/unwind/eh_frame_debug.h
index 40a80861d4d4..bcb2d03ab9ab 100644
--- a/kernel/unwind/eh_frame_debug.h
+++ b/kernel/unwind/eh_frame_debug.h
@@ -14,6 +14,9 @@
 #define dbg_sec(fmt, ...)                                              \
        dbg("%s: " fmt, sec->filename, ##__VA_ARGS__)
 
+#define dbg_sec_ehfh(addr, fmt, ...)                                   \
+       dbg_sec(".eh_frame_hdr+%#lx: " fmt, ((addr) - sec->eh_frame_hdr_start), 
##__VA_ARGS__)
+
 static inline void dbg_init(struct eh_frame_section *sec)
 {
        struct mm_struct *mm = current->mm;
@@ -47,6 +50,7 @@ static inline void dbg_free(struct eh_frame_section *sec)
 
 #define dbg(args...)                   no_printk(args)
 #define dbg_sec(args...)               no_printk(args)
+#define dbg_sec_ehfh(args...)          no_printk(args)
 
 static inline void dbg_init(struct eh_frame_section *sec) {}
 static inline void dbg_free(struct eh_frame_section *sec) {}
-- 
2.53.0


Reply via email to