ptrace: Move setting/clearing ptrace_message into ptrace_stop
authorEric W. Biederman <ebiederm@xmission.com>
Thu, 27 Jan 2022 18:15:32 +0000 (12:15 -0600)
committerEric W. Biederman <ebiederm@xmission.com>
Fri, 18 Mar 2022 14:44:19 +0000 (09:44 -0500)
Today ptrace_message is easy to overlook as it not a core part of
ptrace_stop.  It has been overlooked so much that there are places
that set ptrace_message and don't clear it, and places that never set
it.  So if you get an unlucky sequence of events the ptracer may be
able to read a ptrace_message that does not apply to the current
ptrace stop.

Move setting of ptrace_message into ptrace_stop so that it always gets
set before the stop, and always gets cleared after the stop.  This
prevents non-sense from being reported to userspace and makes
ptrace_message more visible in the ptrace helper functions so that
kernel developers can see it.

Link: https://lkml.kernel.org/r/87bky67qfv.fsf_-_@email.froward.int.ebiederm.org
Acked-by: Oleg Nesterov <oleg@redhat.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
include/linux/ptrace.h
include/uapi/linux/ptrace.h
kernel/signal.c

index 5310f43..3e6b46e 100644 (file)
@@ -60,7 +60,7 @@ extern int ptrace_writedata(struct task_struct *tsk, char __user *src, unsigned
 extern void ptrace_disable(struct task_struct *);
 extern int ptrace_request(struct task_struct *child, long request,
                          unsigned long addr, unsigned long data);
-extern void ptrace_notify(int exit_code);
+extern void ptrace_notify(int exit_code, unsigned long message);
 extern void __ptrace_link(struct task_struct *child,
                          struct task_struct *new_parent,
                          const struct cred *ptracer_cred);
@@ -155,8 +155,7 @@ static inline bool ptrace_event_enabled(struct task_struct *task, int event)
 static inline void ptrace_event(int event, unsigned long message)
 {
        if (unlikely(ptrace_event_enabled(current, event))) {
-               current->ptrace_message = message;
-               ptrace_notify((event << 8) | SIGTRAP);
+               ptrace_notify((event << 8) | SIGTRAP, message);
        } else if (event == PTRACE_EVENT_EXEC) {
                /* legacy EXEC report via SIGTRAP */
                if ((current->ptrace & (PT_PTRACED|PT_SEIZED)) == PT_PTRACED)
@@ -424,8 +423,7 @@ static inline int ptrace_report_syscall(unsigned long message)
        if (!(ptrace & PT_PTRACED))
                return 0;
 
-       current->ptrace_message = message;
-       ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0));
+       ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0), message);
 
        /*
         * this isn't the same as continuing with a signal, but it will do
@@ -437,7 +435,6 @@ static inline int ptrace_report_syscall(unsigned long message)
                current->exit_code = 0;
        }
 
-       current->ptrace_message = 0;
        return fatal_signal_pending(current);
 }
 
index b7af92e..195ae64 100644 (file)
@@ -114,7 +114,7 @@ struct ptrace_rseq_configuration {
 
 /*
  * These values are stored in task->ptrace_message
- * by ptrace_report_syscall_* to describe the current syscall-stop.
+ * by ptrace_stop to describe the current syscall-stop.
  */
 #define PTRACE_EVENTMSG_SYSCALL_ENTRY  1
 #define PTRACE_EVENTMSG_SYSCALL_EXIT   2
index c2dee54..a49ac71 100644 (file)
@@ -2191,7 +2191,8 @@ static void do_notify_parent_cldstop(struct task_struct *tsk,
  * If we actually decide not to stop at all because the tracer
  * is gone, we keep current->exit_code unless clear_code.
  */
-static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t *info)
+static void ptrace_stop(int exit_code, int why, int clear_code,
+                       unsigned long message, kernel_siginfo_t *info)
        __releases(&current->sighand->siglock)
        __acquires(&current->sighand->siglock)
 {
@@ -2237,6 +2238,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t
         */
        smp_wmb();
 
+       current->ptrace_message = message;
        current->last_siginfo = info;
        current->exit_code = exit_code;
 
@@ -2315,6 +2317,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t
         */
        spin_lock_irq(&current->sighand->siglock);
        current->last_siginfo = NULL;
+       current->ptrace_message = 0;
 
        /* LISTENING can be set only during STOP traps, clear it */
        current->jobctl &= ~JOBCTL_LISTENING;
@@ -2327,7 +2330,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t
        recalc_sigpending_tsk(current);
 }
 
-static void ptrace_do_notify(int signr, int exit_code, int why)
+static void ptrace_do_notify(int signr, int exit_code, int why, unsigned long message)
 {
        kernel_siginfo_t info;
 
@@ -2338,17 +2341,17 @@ static void ptrace_do_notify(int signr, int exit_code, int why)
        info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
 
        /* Let the debugger run.  */
-       ptrace_stop(exit_code, why, 1, &info);
+       ptrace_stop(exit_code, why, 1, message, &info);
 }
 
-void ptrace_notify(int exit_code)
+void ptrace_notify(int exit_code, unsigned long message)
 {
        BUG_ON((exit_code & (0x7f | ~0xffff)) != SIGTRAP);
        if (unlikely(task_work_pending(current)))
                task_work_run();
 
        spin_lock_irq(&current->sighand->siglock);
-       ptrace_do_notify(SIGTRAP, exit_code, CLD_TRAPPED);
+       ptrace_do_notify(SIGTRAP, exit_code, CLD_TRAPPED, message);
        spin_unlock_irq(&current->sighand->siglock);
 }
 
@@ -2504,10 +2507,10 @@ static void do_jobctl_trap(void)
                        signr = SIGTRAP;
                WARN_ON_ONCE(!signr);
                ptrace_do_notify(signr, signr | (PTRACE_EVENT_STOP << 8),
-                                CLD_STOPPED);
+                                CLD_STOPPED, 0);
        } else {
                WARN_ON_ONCE(!signr);
-               ptrace_stop(signr, CLD_STOPPED, 0, NULL);
+               ptrace_stop(signr, CLD_STOPPED, 0, 0, NULL);
                current->exit_code = 0;
        }
 }
@@ -2561,7 +2564,7 @@ static int ptrace_signal(int signr, kernel_siginfo_t *info, enum pid_type type)
         * comment in dequeue_signal().
         */
        current->jobctl |= JOBCTL_STOP_DEQUEUED;
-       ptrace_stop(signr, CLD_TRAPPED, 0, info);
+       ptrace_stop(signr, CLD_TRAPPED, 0, 0, info);
 
        /* We're back.  Did the debugger cancel the sig?  */
        signr = current->exit_code;
@@ -2891,7 +2894,7 @@ static void signal_delivered(struct ksignal *ksig, int stepping)
        if (current->sas_ss_flags & SS_AUTODISARM)
                sas_ss_reset(current);
        if (stepping)
-               ptrace_notify(SIGTRAP);
+               ptrace_notify(SIGTRAP, 0);
 }
 
 void signal_setup_done(int failed, struct ksignal *ksig, int stepping)