mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Oleg Nesterov <oleg@redhat.com>
To: Tejun Heo <tj@kernel.org>
Cc: roland@redhat.com, linux-kernel@vger.kernel.org,
	torvalds@linux-foundation.org, akpm@linux-foundation.org,
	rjw@sisk.pl, jan.kratochvil@redhat.com
Subject: Re: [PATCH 10/16] ptrace: clean transitions between TASK_STOPPED and TRACED
Date: Mon, 20 Dec 2010 16:00:37 +0100	[thread overview]
Message-ID: <20101220150037.GE11583@redhat.com> (raw)
In-Reply-To: <1291654624-6230-11-git-send-email-tj@kernel.org>

On 12/06, Tejun Heo wrote:
>
> @@ -93,6 +99,7 @@ int ptrace_check_attach(struct task_struct *child, int kill)
>  	 * we are sure that this is our traced child and that can only
>  	 * be changed by us so it's not changing right after this.
>  	 */
> +relock:
>  	read_lock(&tasklist_lock);
>  	if ((child->ptrace & PT_PTRACED) && child->parent == current) {
>  		ret = 0;
> @@ -101,10 +108,30 @@ int ptrace_check_attach(struct task_struct *child, int kill)
>  		 * does ptrace_unlink() before __exit_signal().
>  		 */
>  		spin_lock_irq(&child->sighand->siglock);
> -		if (task_is_stopped(child))
> -			child->state = TASK_TRACED;
> -		else if (!task_is_traced(child) && !kill)
> +		if (!task_is_traced(child) && !kill) {
> +			/*
> +			 * If GROUP_STOP_TRAPPING is set, it is known that
> +			 * the tracee will enter either TRACED or the bit
> +			 * will be cleared in definite amount of (userland)
> +			 * time.  Wait while the bit is set.
> +			 *
> +			 * This hides PTRACE_ATTACH initiated transition
> +			 * from STOPPED to TRACED from userland.
> +			 */
> +			if (child->group_stop & GROUP_STOP_TRAPPING) {
> +				const int bit = ilog2(GROUP_STOP_TRAPPING);
> +				DEFINE_WAIT_BIT(wait, &child->group_stop, bit);

Unused "wait_bit_queue wait"

> +
> +				spin_unlock_irq(&child->sighand->siglock);
> +				read_unlock(&tasklist_lock);
> +
> +				wait_on_bit(&child->group_stop, bit,

Hmm. we could probably use ->wait_chldexit/__wake_up_parent instead,
although I am not sure this would be more clean...

> +					    ptrace_wait_trap,
> +					    TASK_UNINTERRUPTIBLE);

I am nervous ;) To me, TASK_KILLABLE makes more sense, and it is
safer if we have a bug.

> +				goto relock;

We already set ret = 0. "relock" should set -ESRCH.

> +			}
>  			ret = -ESRCH;
> +		}

Probably this deserves a minor cleanup,

	relock:
		ret = -ESRCH;
		read_lock(&tasklist_lock);
		if (task_is_traced() || kill) {
			ret = 0;
		} else {
		...


OK. So, ptrace_attach() asks a stopped tracee to s/STOPPED/TRACED/.
If it is not stopped, it should call ptrace_stop() eventually.

This doesn't work if ptrace_attach() races with clone(CLONE_STOPPED).
ptrace_check_attach() can return the wrong ESRCH after that. Perhaps
it is time to kill the CLONE_STOPPED code in do_fork().

> @@ -204,6 +231,26 @@ int ptrace_attach(struct task_struct *task)
>  	__ptrace_link(task, current);
>  	send_sig_info(SIGSTOP, SEND_SIG_FORCED, task);
>
> +	spin_lock(&task->sighand->siglock);
> +
> +	/*
> +	 * If the task is already STOPPED, set GROUP_STOP_PENDING and
> +	 * TRAPPING, and kick it so that it transits to TRACED.  TRAPPING
> +	 * will be cleared if the child completes the transition or any
> +	 * event which clears the group stop states happens.  The bit is
> +	 * waited by ptrace_check_attach() to hide the transition from
> +	 * userland.
> +	 *
> +	 * The following is safe as both transitions in and out of STOPPED
> +	 * are protected by siglock.
> +	 */
> +	if (task_is_stopped(task)) {
> +		task->group_stop |= GROUP_STOP_PENDING | GROUP_STOP_TRAPPING;
> +		signal_wake_up(task, 1);
> +	}
> +
> +	spin_unlock(&task->sighand->siglock);

Well. I do not know whether this matters, but "hide the transition from
userland" is not 100% correct. I mean, this change is still visible.

ptrace_check_attach()->wait_on_bit() logic fixes the previous example,
but:

	1. the tracer knows that the tracee is stopped

	2. the tracer does ptrace(ATTACH)

	3. the tracer does do_wait()

In this case do_wait() can see the tracee in TASK_RUNNING state,
this breaks wait_task_stopped(ptrace => true).

Jan?

> @@ -1799,22 +1830,28 @@ static int do_signal_stop(int signr)
>  		 */
>  		sig->group_exit_code = signr;
>
> -		current->group_stop = gstop;
> +		current->group_stop &= ~GROUP_STOP_SIGMASK;
> +		current->group_stop |= signr | gstop;
>  		sig->group_stop_count = 1;
> -		for (t = next_thread(current); t != current; t = next_thread(t))
> +		for (t = next_thread(current); t != current;
> +		     t = next_thread(t)) {
> +			t->group_stop &= ~GROUP_STOP_SIGMASK;
>  			/*
>  			 * Setting state to TASK_STOPPED for a group
>  			 * stop is always done with the siglock held,
>  			 * so this check has no races.
>  			 */
>  			if (!(t->flags & PF_EXITING) && !task_is_stopped(t)) {
> -				t->group_stop = gstop;
> +				t->group_stop |= signr | gstop;
>  				sig->group_stop_count++;
>  				signal_wake_up(t, 0);
> -			} else
> +			} else {
>  				task_clear_group_stop(t);

This looks racy. Suppose that "current" is ptraced, in this case
it can initiate the new group-stop even if SIGNAL_STOP_STOPPED
is set and we have another TASK_STOPPED thead T.

Suppose that another (or same) debugger ataches to this thread T,
wakes it up and sets GROUP_STOP_TRAPPING.

T resumes, calls ptrace_stop() in TASK_STOPPED, and temporary drops
->siglock.

Now, this task_clear_group_stop(T) confuses ptrace_check_attach(T).

I think ptrace_stop() should be called in TASK_RUNNING state.
This also makes sense because we may call arch_ptrace_stop().

> +				t->group_stop |= signr;

Probably this doesn't really matter, but why do we need to
change the GROUP_STOP_SIGMASK part of t->group_stop? If it
is exiting, this is not needed. If it is already stopped, then
it already has the correct (previous) signr.

> +			}
> +		}
>  	}
> -
> +retry:
>  	current->exit_code = sig->group_exit_code;
>  	__set_current_state(TASK_STOPPED);

It is no longer needed to set ->exit_code here. The only reason
was s/STOPPED/TRACED/ change in ptrace_check_attach(). Now that
we rely on ptrace_stop() which sets ->exit_state, this can be
removed.

And,

> @@ -1842,7 +1879,18 @@ static int do_signal_stop(int signr)
>
>  		spin_lock_irq(&current->sighand->siglock);
>  	} else
> -		ptrace_stop(current->exit_code, CLD_STOPPED, 0, NULL);
> +		ptrace_stop(current->group_stop & GROUP_STOP_SIGMASK,
> +			    CLD_STOPPED, 0, NULL);

Perhaps it would be more clean to clear ->exit_code here, in the
"else" branch.

Oleg.


  reply	other threads:[~2010-12-20 15:07 UTC|newest]

Thread overview: 62+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-12-06 16:56 [PATCHSET] ptrace,signal: sane interaction between ptrace and job control signals, take#2 Tejun Heo
2010-12-06 16:56 ` [PATCH 01/16] signal: fix SIGCONT notification code Tejun Heo
2010-12-06 16:56 ` [PATCH 02/16] signal: fix CLD_CONTINUED notification target Tejun Heo
2010-12-20 14:58   ` Oleg Nesterov
2010-12-21 16:31     ` Tejun Heo
2010-12-06 16:56 ` [PATCH 03/16] signal: remove superflous try_to_freeze() loop in do_signal_stop() Tejun Heo
2010-12-20 14:59   ` Oleg Nesterov
2010-12-06 16:56 ` [PATCH 04/16] ptrace: kill tracehook_notify_jctl() Tejun Heo
2010-12-20 14:59   ` Oleg Nesterov
2010-12-21 17:00     ` Tejun Heo
2010-12-06 16:56 ` [PATCH 05/16] ptrace: add @why to ptrace_stop() Tejun Heo
2010-12-06 16:56 ` [PATCH 06/16] signal: fix premature completion of group stop when interfered by ptrace Tejun Heo
2010-12-20 15:00   ` Oleg Nesterov
2010-12-21 17:04     ` Tejun Heo
2010-12-06 16:56 ` [PATCH 07/16] signal: use GROUP_STOP_PENDING to stop once for a single group stop Tejun Heo
2010-12-06 16:56 ` [PATCH 08/16] ptrace: participate in group stop from ptrace_stop() iff the task is trapping for " Tejun Heo
2010-12-06 16:56 ` [PATCH 09/16] ptrace: make do_signal_stop() use ptrace_stop() if the task is being ptraced Tejun Heo
2010-12-23 12:26   ` Oleg Nesterov
2010-12-23 13:53     ` Tejun Heo
2010-12-23 16:06       ` Oleg Nesterov
2010-12-23 16:33         ` Tejun Heo
2011-01-17 22:09     ` Roland McGrath
2011-01-27 13:56       ` Tejun Heo
2011-01-28 20:30         ` Roland McGrath
2011-01-31 14:39           ` Tejun Heo
2010-12-06 16:56 ` [PATCH 10/16] ptrace: clean transitions between TASK_STOPPED and TRACED Tejun Heo
2010-12-20 15:00   ` Oleg Nesterov [this message]
2010-12-21 17:31     ` Tejun Heo
2010-12-21 17:32       ` Tejun Heo
2010-12-22 10:54       ` Tejun Heo
2010-12-22 11:39       ` Oleg Nesterov
2010-12-22 15:14         ` Tejun Heo
2010-12-22 16:00           ` Oleg Nesterov
2010-12-22 16:21             ` Tejun Heo
2010-12-06 16:56 ` [PATCH 11/16] signal: prepare for CLD_* notification changes Tejun Heo
2010-12-20 16:21   ` Oleg Nesterov
2010-12-20 16:23     ` Oleg Nesterov
2010-12-21 17:35     ` Tejun Heo
2010-12-06 16:57 ` [PATCH 12/16] ptrace: make group stop notification reliable against ptrace Tejun Heo
2010-12-20 17:34   ` Oleg Nesterov
2010-12-21 17:43     ` Tejun Heo
2010-12-22 11:54       ` Oleg Nesterov
2010-12-22 15:26         ` Tejun Heo
2010-12-22 16:02           ` Oleg Nesterov
2010-12-06 16:57 ` [PATCH 13/16] ptrace: reorganize __ptrace_unlink() and ptrace_untrace() Tejun Heo
2010-12-20 18:15   ` Oleg Nesterov
2010-12-21 17:54     ` Tejun Heo
2010-12-06 16:57 ` [PATCH 14/16] ptrace: make SIGCONT notification reliable against ptrace Tejun Heo
2010-12-20 19:43   ` Oleg Nesterov
2010-12-21 17:48     ` Tejun Heo
2010-12-22 12:16       ` Oleg Nesterov
2010-12-21 17:25   ` Oleg Nesterov
2010-12-22 10:35     ` Tejun Heo
2010-12-06 16:57 ` [PATCH 15/16] ptrace: make sure SIGNAL_NOTIFY_CONT is checked after ptrace_signal() Tejun Heo
2010-12-06 16:57 ` [PATCH 16/16] ptrace: remove the extra wake_up_process() from ptrace_detach() Tejun Heo
2010-12-07  0:10   ` Roland McGrath
2010-12-07 13:43     ` Tejun Heo
2010-12-21 17:54   ` Oleg Nesterov
2010-12-22 10:36     ` Tejun Heo
2010-12-14 17:36 ` [PATCHSET] ptrace,signal: sane interaction between ptrace and job control signals, take#2 Oleg Nesterov
2010-12-14 17:46   ` Tejun Heo
2010-12-22 15:20 ` Oleg Nesterov

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=20101220150037.GE11583@redhat.com \
    --to=oleg@redhat.com \
    --cc=akpm@linux-foundation.org \
    --cc=jan.kratochvil@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=rjw@sisk.pl \
    --cc=roland@redhat.com \
    --cc=tj@kernel.org \
    --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

Powered by JetHome