]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - arch/x86/kernel/stacktrace.c
stacktrace/x86: add function for detecting reliable stack traces
[karo-tx-linux.git] / arch / x86 / kernel / stacktrace.c
index 8e2b79b88e512f2636ae76075895405c7a2aa2fc..8dabd7bf16730918f1a8c91fcd5df503d73b056a 100644 (file)
@@ -76,6 +76,101 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 }
 EXPORT_SYMBOL_GPL(save_stack_trace_tsk);
 
+#ifdef CONFIG_HAVE_RELIABLE_STACKTRACE
+
+#define STACKTRACE_DUMP_ONCE(task) ({                          \
+       static bool __section(.data.unlikely) __dumped;         \
+                                                               \
+       if (!__dumped) {                                        \
+               __dumped = true;                                \
+               WARN_ON(1);                                     \
+               show_stack(task, NULL);                         \
+       }                                                       \
+})
+
+static int __save_stack_trace_reliable(struct stack_trace *trace,
+                                      struct task_struct *task)
+{
+       struct unwind_state state;
+       struct pt_regs *regs;
+       unsigned long addr;
+
+       for (unwind_start(&state, task, NULL, NULL); !unwind_done(&state);
+            unwind_next_frame(&state)) {
+
+               regs = unwind_get_entry_regs(&state);
+               if (regs) {
+                       /*
+                        * Kernel mode registers on the stack indicate an
+                        * in-kernel interrupt or exception (e.g., preemption
+                        * or a page fault), which can make frame pointers
+                        * unreliable.
+                        */
+                       if (!user_mode(regs))
+                               return -EINVAL;
+
+                       /*
+                        * The last frame contains the user mode syscall
+                        * pt_regs.  Skip it and finish the unwind.
+                        */
+                       unwind_next_frame(&state);
+                       if (!unwind_done(&state)) {
+                               STACKTRACE_DUMP_ONCE(task);
+                               return -EINVAL;
+                       }
+                       break;
+               }
+
+               addr = unwind_get_return_address(&state);
+
+               /*
+                * A NULL or invalid return address probably means there's some
+                * generated code which __kernel_text_address() doesn't know
+                * about.
+                */
+               if (!addr) {
+                       STACKTRACE_DUMP_ONCE(task);
+                       return -EINVAL;
+               }
+
+               if (save_stack_address(trace, addr, false))
+                       return -EINVAL;
+       }
+
+       /* Check for stack corruption */
+       if (unwind_error(&state)) {
+               STACKTRACE_DUMP_ONCE(task);
+               return -EINVAL;
+       }
+
+       if (trace->nr_entries < trace->max_entries)
+               trace->entries[trace->nr_entries++] = ULONG_MAX;
+
+       return 0;
+}
+
+/*
+ * This function returns an error if it detects any unreliable features of the
+ * stack.  Otherwise it guarantees that the stack trace is reliable.
+ *
+ * If the task is not 'current', the caller *must* ensure the task is inactive.
+ */
+int save_stack_trace_tsk_reliable(struct task_struct *tsk,
+                                 struct stack_trace *trace)
+{
+       int ret;
+
+       if (!try_get_task_stack(tsk))
+               return -EINVAL;
+
+       ret = __save_stack_trace_reliable(trace, tsk);
+
+       put_task_stack(tsk);
+
+       return ret;
+}
+#endif /* CONFIG_HAVE_RELIABLE_STACKTRACE */
+
 /* Userspace stacktrace - based on kernel/trace/trace_sysprof.c */
 
 struct stack_frame_user {
@@ -138,4 +233,3 @@ void save_stack_trace_user(struct stack_trace *trace)
        if (trace->nr_entries < trace->max_entries)
                trace->entries[trace->nr_entries++] = ULONG_MAX;
 }
-