Patch "ptrace: make ptrace() fail if the tracee changed its pid unexpectedly" has been added to the 4.14-stable tree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a note to let you know that I've just added the patch titled

    ptrace: make ptrace() fail if the tracee changed its pid unexpectedly

to the 4.14-stable tree which can be found at:
    http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary

The filename of the patch is:
     ptrace-make-ptrace-fail-if-the-tracee-changed-its-pi.patch
and it can be found in the queue-4.14 subdirectory.

If you, or anyone else, feels it should not be added to the stable tree,
please let <stable@xxxxxxxxxxxxxxx> know about it.



commit 83ec1997898d85334e02f484acc2115d4ad24f3f
Author: Oleg Nesterov <oleg@xxxxxxxxxx>
Date:   Wed May 12 15:33:08 2021 +0200

    ptrace: make ptrace() fail if the tracee changed its pid unexpectedly
    
    [ Upstream commit dbb5afad100a828c97e012c6106566d99f041db6 ]
    
    Suppose we have 2 threads, the group-leader L and a sub-theread T,
    both parked in ptrace_stop(). Debugger tries to resume both threads
    and does
    
            ptrace(PTRACE_CONT, T);
            ptrace(PTRACE_CONT, L);
    
    If the sub-thread T execs in between, the 2nd PTRACE_CONT doesn not
    resume the old leader L, it resumes the post-exec thread T which was
    actually now stopped in PTHREAD_EVENT_EXEC. In this case the
    PTHREAD_EVENT_EXEC event is lost, and the tracer can't know that the
    tracee changed its pid.
    
    This patch makes ptrace() fail in this case until debugger does wait()
    and consumes PTHREAD_EVENT_EXEC which reports old_pid. This affects all
    ptrace requests except the "asynchronous" PTRACE_INTERRUPT/KILL.
    
    The patch doesn't add the new PTRACE_ option to not complicate the API,
    and I _hope_ this won't cause any noticeable regression:
    
            - If debugger uses PTRACE_O_TRACEEXEC and the thread did an exec
              and the tracer does a ptrace request without having consumed
              the exec event, it's 100% sure that the thread the ptracer
              thinks it is targeting does not exist anymore, or isn't the
              same as the one it thinks it is targeting.
    
            - To some degree this patch adds nothing new. In the scenario
              above ptrace(L) can fail with -ESRCH if it is called after the
              execing sub-thread wakes the leader up and before it "steals"
              the leader's pid.
    
    Test-case:
    
            #include <stdio.h>
            #include <unistd.h>
            #include <signal.h>
            #include <sys/ptrace.h>
            #include <sys/wait.h>
            #include <errno.h>
            #include <pthread.h>
            #include <assert.h>
    
            void *tf(void *arg)
            {
                    execve("/usr/bin/true", NULL, NULL);
                    assert(0);
    
                    return NULL;
            }
    
            int main(void)
            {
                    int leader = fork();
                    if (!leader) {
                            kill(getpid(), SIGSTOP);
    
                            pthread_t th;
                            pthread_create(&th, NULL, tf, NULL);
                            for (;;)
                                    pause();
    
                            return 0;
                    }
    
                    waitpid(leader, NULL, WSTOPPED);
    
                    ptrace(PTRACE_SEIZE, leader, 0,
                                    PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC);
                    waitpid(leader, NULL, 0);
    
                    ptrace(PTRACE_CONT, leader, 0,0);
                    waitpid(leader, NULL, 0);
    
                    int status, thread = waitpid(-1, &status, 0);
                    assert(thread > 0 && thread != leader);
                    assert(status == 0x80137f);
    
                    ptrace(PTRACE_CONT, thread, 0,0);
                    /*
                     * waitid() because waitpid(leader, &status, WNOWAIT) does not
                     * report status. Why ????
                     *
                     * Why WEXITED? because we have another kernel problem connected
                     * to mt-exec.
                     */
                    siginfo_t info;
                    assert(waitid(P_PID, leader, &info, WSTOPPED|WEXITED|WNOWAIT) == 0);
                    assert(info.si_pid == leader && info.si_status == 0x0405);
    
                    /* OK, it sleeps in ptrace(PTRACE_EVENT_EXEC == 0x04) */
                    assert(ptrace(PTRACE_CONT, leader, 0,0) == -1);
                    assert(errno == ESRCH);
    
                    assert(leader == waitpid(leader, &status, WNOHANG));
                    assert(status == 0x04057f);
    
                    assert(ptrace(PTRACE_CONT, leader, 0,0) == 0);
    
                    return 0;
            }
    
    Signed-off-by: Oleg Nesterov <oleg@xxxxxxxxxx>
    Reported-by: Simon Marchi <simon.marchi@xxxxxxxxxxxx>
    Acked-by: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx>
    Acked-by: Pedro Alves <palves@xxxxxxxxxx>
    Acked-by: Simon Marchi <simon.marchi@xxxxxxxxxxxx>
    Acked-by: Jan Kratochvil <jan.kratochvil@xxxxxxxxxx>
    Signed-off-by: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx>
    Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>

diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 43a283041296..b28f3c66c6fe 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -163,6 +163,21 @@ void __ptrace_unlink(struct task_struct *child)
 	spin_unlock(&child->sighand->siglock);
 }
 
+static bool looks_like_a_spurious_pid(struct task_struct *task)
+{
+	if (task->exit_code != ((PTRACE_EVENT_EXEC << 8) | SIGTRAP))
+		return false;
+
+	if (task_pid_vnr(task) == task->ptrace_message)
+		return false;
+	/*
+	 * The tracee changed its pid but the PTRACE_EVENT_EXEC event
+	 * was not wait()'ed, most probably debugger targets the old
+	 * leader which was destroyed in de_thread().
+	 */
+	return true;
+}
+
 /* Ensure that nothing can wake it up, even SIGKILL */
 static bool ptrace_freeze_traced(struct task_struct *task)
 {
@@ -173,7 +188,8 @@ static bool ptrace_freeze_traced(struct task_struct *task)
 		return ret;
 
 	spin_lock_irq(&task->sighand->siglock);
-	if (task_is_traced(task) && !__fatal_signal_pending(task)) {
+	if (task_is_traced(task) && !looks_like_a_spurious_pid(task) &&
+	    !__fatal_signal_pending(task)) {
 		task->state = __TASK_TRACED;
 		ret = true;
 	}



[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux