When duplicating a process' virtual memory mappings also duplicate all
of its registered .sframe sections stored in the per-mm maple tree to
enable stacktracing using sframe of the child process.

Signed-off-by: Jens Remus <[email protected]>
---
 include/linux/sframe.h       |  5 ++++
 kernel/unwind/sframe.c       | 48 ++++++++++++++++++++++++++++++++++++
 kernel/unwind/sframe_debug.h |  7 ++++++
 mm/mmap.c                    |  9 +++++++
 4 files changed, 69 insertions(+)

diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index b79c5ec09229..91889b4fe3dd 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -28,6 +28,7 @@ struct sframe_section {
 };
 
 #define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+extern int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm);
 extern void sframe_free_mm(struct mm_struct *mm);
 
 extern int sframe_add_section(unsigned long sframe_start, unsigned long 
sframe_end,
@@ -45,6 +46,10 @@ static inline bool current_has_sframe(void)
 #else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
 
 #define INIT_MM_SFRAME
+static inline int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm)
+{
+       return 0;
+}
 static inline void sframe_free_mm(struct mm_struct *mm) {}
 static inline int sframe_add_section(unsigned long sframe_start, unsigned long 
sframe_end,
                                     unsigned long text_start, unsigned long 
text_end)
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index ec8318977a2e..0661090dd5bc 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -886,6 +886,54 @@ int sframe_remove_section(unsigned long sframe_start)
        return 0;
 }
 
+static void __sframe_dup_section(struct sframe_section *sec, struct 
sframe_section *oldsec)
+{
+       sec->sframe_start       = oldsec->sframe_start;
+       sec->sframe_end         = oldsec->sframe_end;
+       sec->text_start         = oldsec->text_start;
+       sec->text_end           = oldsec->text_end;
+
+       sec->fdes_start         = oldsec->fdes_start;
+       sec->fres_start         = oldsec->fres_start;
+       sec->fres_end           = oldsec->fres_end;
+       sec->num_fdes           = oldsec->num_fdes;
+
+       sec->ra_off             = oldsec->ra_off;
+       sec->fp_off             = oldsec->fp_off;
+
+       dbg_dup(sec, oldsec);
+}
+
+int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm)
+{
+       struct sframe_section *sec, *oldsec;
+       unsigned long index = 0;
+       int ret;
+
+       guard(srcu)(&sframe_srcu);
+
+       mt_for_each(&oldmm->sframe_mt, oldsec, index, ULONG_MAX) {
+               sec = kzalloc(sizeof(*sec), GFP_KERNEL_ACCOUNT);
+               if (!sec)
+                       return -ENOMEM;
+
+               __sframe_dup_section(sec, oldsec);
+
+               ret = mtree_insert_range(&mm->sframe_mt,
+                                        sec->text_start,
+                                        sec->text_end - 1,
+                                        sec, GFP_KERNEL_ACCOUNT);
+               if (ret)
+                       goto err_free;
+       }
+
+       return 0;
+
+err_free:
+       free_section(sec);
+       return ret;
+}
+
 void sframe_free_mm(struct mm_struct *mm)
 {
        struct sframe_section *sec;
diff --git a/kernel/unwind/sframe_debug.h b/kernel/unwind/sframe_debug.h
index a63e75cccc70..2503972155e8 100644
--- a/kernel/unwind/sframe_debug.h
+++ b/kernel/unwind/sframe_debug.h
@@ -48,6 +48,12 @@ static inline void dbg_init(struct sframe_section *sec)
                sec->filename = kstrdup("(anonymous)", GFP_KERNEL_ACCOUNT);
 }
 
+static inline void dbg_dup(struct sframe_section *sec, struct sframe_section 
*oldsec)
+{
+       if (oldsec->filename)
+               sec->filename = kstrdup(oldsec->filename, GFP_KERNEL_ACCOUNT);
+}
+
 static inline void dbg_free(struct sframe_section *sec)
 {
        kfree(sec->filename);
@@ -61,6 +67,7 @@ static inline void dbg_free(struct sframe_section *sec)
 static inline void dbg_print_header(struct sframe_section *sec) {}
 
 static inline void dbg_init(struct sframe_section *sec) {}
+static inline void dbg_dup(struct sframe_section *sec, struct sframe_section 
*oldsec) {}
 static inline void dbg_free(struct sframe_section *sec) {}
 
 #endif /* !CONFIG_DYNAMIC_DEBUG */
diff --git a/mm/mmap.c b/mm/mmap.c
index 5754d1c36462..250e8bea73ef 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -48,6 +48,7 @@
 #include <linux/sched/mm.h>
 #include <linux/ksm.h>
 #include <linux/memfd.h>
+#include <linux/sframe.h>
 
 #include <linux/uaccess.h>
 #include <asm/cacheflush.h>
@@ -1846,6 +1847,11 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, 
struct mm_struct *oldmm)
        }
        /* a new mm has just been created */
        retval = arch_dup_mmap(oldmm, mm);
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+       if (!retval) {
+               retval = sframe_dup_mm(mm, oldmm);
+       }
+#endif
 loop_out:
        vma_iter_free(&vmi);
        if (!retval) {
@@ -1893,6 +1899,9 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, 
struct mm_struct *oldmm)
                        vm_unacct_memory(charge);
                }
                __mt_destroy(&mm->mm_mt);
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+               sframe_free_mm(mm);
+#endif
                /*
                 * The mm_struct is going to exit, but the locks will be dropped
                 * first.  Set the mm_struct as unstable is advisable as it is
-- 
2.51.0


Reply via email to