Commit 0e9f0a4a authored by Tejun Heo's avatar Tejun Heo

ptrace: Always put ptracee into appropriate execution state

Currently, __ptrace_unlink() wakes up the tracee iff it's in
TASK_TRACED.  For unlinking from PTRACE_DETACH, this is correct as the
tracee is guaranteed to be in TASK_TRACED or dead; however, unlinking
also happens when the ptracer exits and in this case the ptracee can
be in any state and ptrace might be left running even if the group it
belongs to is stopped.

This patch updates __ptrace_unlink() such that GROUP_STOP_PENDING is
reinstated regardless of the ptracee's current state as long as it's
alive and makes sure that signal_wake_up() is called if execution
state transition is necessary.

Test case follows.

  #include <unistd.h>
  #include <time.h>
  #include <sys/types.h>
  #include <sys/ptrace.h>
  #include <sys/wait.h>

  static const struct timespec ts1s = { .tv_sec = 1 };

  int main(void)
  {
	  pid_t tracee;
	  siginfo_t si;

	  tracee = fork();
	  if (tracee == 0) {
		  while (1) {
			  nanosleep(&ts1s, NULL);
			  write(1, ".", 1);
		  }
	  }

	  ptrace(PTRACE_ATTACH, tracee, NULL, NULL);
	  waitid(P_PID, tracee, &si, WSTOPPED);
	  ptrace(PTRACE_CONT, tracee, NULL, (void *)(long)si.si_status);
	  waitid(P_PID, tracee, &si, WSTOPPED);
	  ptrace(PTRACE_CONT, tracee, NULL, (void *)(long)si.si_status);
	  write(1, "exiting", 7);
	  return 0;
  }

Before the patch, after the parent process exits, the child is left
running and prints out "." every second.

  exiting..... (continues)

After the patch, the group stop initiated by the implied SIGSTOP from
PTRACE_ATTACH is re-established when the parent exits.

  exiting
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
Reported-by: default avatarOleg Nesterov <oleg@redhat.com>
Acked-by: default avatarOleg Nesterov <oleg@redhat.com>
parent e3bd058f
...@@ -41,7 +41,26 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent) ...@@ -41,7 +41,26 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent)
* __ptrace_unlink - unlink ptracee and restore its execution state * __ptrace_unlink - unlink ptracee and restore its execution state
* @child: ptracee to be unlinked * @child: ptracee to be unlinked
* *
* Remove @child from the ptrace list, move it back to the original parent. * Remove @child from the ptrace list, move it back to the original parent,
* and restore the execution state so that it conforms to the group stop
* state.
*
* Unlinking can happen via two paths - explicit PTRACE_DETACH or ptracer
* exiting. For PTRACE_DETACH, unless the ptracee has been killed between
* ptrace_check_attach() and here, it's guaranteed to be in TASK_TRACED.
* If the ptracer is exiting, the ptracee can be in any state.
*
* After detach, the ptracee should be in a state which conforms to the
* group stop. If the group is stopped or in the process of stopping, the
* ptracee should be put into TASK_STOPPED; otherwise, it should be woken
* up from TASK_TRACED.
*
* If the ptracee is in TASK_TRACED and needs to be moved to TASK_STOPPED,
* it goes through TRACED -> RUNNING -> STOPPED transition which is similar
* to but in the opposite direction of what happens while attaching to a
* stopped task. However, in this direction, the intermediate RUNNING
* state is not hidden even from the current ptracer and if it immediately
* re-attaches and performs a WNOHANG wait(2), it may fail.
* *
* CONTEXT: * CONTEXT:
* write_lock_irq(tasklist_lock) * write_lock_irq(tasklist_lock)
...@@ -55,25 +74,25 @@ void __ptrace_unlink(struct task_struct *child) ...@@ -55,25 +74,25 @@ void __ptrace_unlink(struct task_struct *child)
list_del_init(&child->ptrace_entry); list_del_init(&child->ptrace_entry);
spin_lock(&child->sighand->siglock); spin_lock(&child->sighand->siglock);
if (task_is_traced(child)) {
/* /*
* If group stop is completed or in progress, it should * Reinstate GROUP_STOP_PENDING if group stop is in effect and
* participate in the group stop. Set GROUP_STOP_PENDING * @child isn't dead.
* before kicking it.
*
* This involves TRACED -> RUNNING -> STOPPED transition
* which is similar to but in the opposite direction of
* what happens while attaching to a stopped task.
* However, in this direction, the intermediate RUNNING
* state is not hidden even from the current ptracer and if
* it immediately re-attaches and performs a WNOHANG
* wait(2), it may fail.
*/ */
if (child->signal->flags & SIGNAL_STOP_STOPPED || if (!(child->flags & PF_EXITING) &&
child->signal->group_stop_count) (child->signal->flags & SIGNAL_STOP_STOPPED ||
child->signal->group_stop_count))
child->group_stop |= GROUP_STOP_PENDING; child->group_stop |= GROUP_STOP_PENDING;
signal_wake_up(child, 1);
} /*
* If transition to TASK_STOPPED is pending or in TASK_TRACED, kick
* @child in the butt. Note that @resume should be used iff @child
* is in TASK_TRACED; otherwise, we might unduly disrupt
* TASK_KILLABLE sleeps.
*/
if (child->group_stop & GROUP_STOP_PENDING || task_is_traced(child))
signal_wake_up(child, task_is_traced(child));
spin_unlock(&child->sighand->siglock); spin_unlock(&child->sighand->siglock);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment