mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: ebiederm@xmission.com (Eric W. Biederman)
To: Oleg Nesterov <oleg@redhat.com>
Cc: Andrew Morton <akpm@linux-foundation.org>,
	Eugene Syromiatnikov <esyr@redhat.com>,
	Jan Kratochvil <jan.kratochvil@redhat.com>,
	Linus Torvalds <torvalds@linux-foundation.org>,
	Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
	Michael Kerrisk <mtk.manpages@gmail.com>,
	Pedro Alves <palves@redhat.com>,
	Simon Marchi <simon.marchi@efficios.com>,
	linux-kernel@vger.kernel.org
Subject: Re: [RFC PATCH] ptrace: make ptrace() fail if the tracee changed its pid unexpectedly
Date: Thu, 17 Dec 2020 17:39:25 -0600	[thread overview]
Message-ID: <875z50roia.fsf@x220.int.ebiederm.org> (raw)
In-Reply-To: <20201217142931.GA8865@redhat.com> (Oleg Nesterov's message of "Thu, 17 Dec 2020 15:29:31 +0100")

Oleg Nesterov <oleg@redhat.com> writes:

> 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.

The change seems sensible.  I don't expect this is common but it looks
painful to deal with if it happens.

Acked-by: "Eric W. Biederman" <ebiederm@xmission.com>

I am wondering if this should be expanded to all ptrace types for
consistency.  Or maybe we should set a flag to make this happen for
all ptrace events.

It just seems really odd to only worry about missing this event.
I admit this a threaded PTRACE_EVENT_EXEC is the only event we are
likely to miss but still.

Do you by any chance have any debugger/strace test cases?

I would think that would be the way to test to see if this breaks
anything.  I think I remember strace having a good test suite.

> 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@redhat.com>
> ---
>  kernel/ptrace.c | 24 +++++++++++++++++++++++-
>  1 file changed, 23 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/ptrace.c b/kernel/ptrace.c
> index 43d6179508d6..1037251ae4a5 100644
> --- a/kernel/ptrace.c
> +++ b/kernel/ptrace.c
> @@ -169,6 +169,27 @@ void __ptrace_unlink(struct task_struct *child)
>  	spin_unlock(&child->sighand->siglock);
>  }
>  
> +static bool looks_like_a_spurious_pid(struct task_struct *task)
> +{
> +	int pid;
> +
> +	if (task->exit_code != ((PTRACE_EVENT_EXEC << 8) | SIGTRAP))
> +		return false;
> +
> +	rcu_read_lock();
> +	pid = task_pid_nr_ns(task, task_active_pid_ns(task->parent));
> +	rcu_read_unlock();
> +
> +	if (pid == 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)
>  {
> @@ -179,7 +200,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;
>  	}

Eric

  reply	other threads:[~2020-12-17 23:41 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-12-17 14:29 Oleg Nesterov
2020-12-17 23:39 ` Eric W. Biederman [this message]
2020-12-18 14:10   ` Oleg Nesterov
2020-12-21 20:04     ` Eric W. Biederman
2020-12-19 16:19   ` Pedro Alves
2020-12-19 19:33     ` Oleg Nesterov
2020-12-20  4:48       ` Simon Marchi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=875z50roia.fsf@x220.int.ebiederm.org \
    --to=ebiederm@xmission.com \
    --cc=akpm@linux-foundation.org \
    --cc=esyr@redhat.com \
    --cc=jan.kratochvil@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mtk.manpages@gmail.com \
    --cc=oleg@redhat.com \
    --cc=palves@redhat.com \
    --cc=simon.marchi@efficios.com \
    --cc=torvalds@linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®