aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/ptrace.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-03-23 05:37:01 -0400
committerTejun Heo <tj@kernel.org>2011-03-23 05:37:01 -0400
commit0e9f0a4abfd80f8adca624538d479d95159b16d8 (patch)
tree6b9910140f4755cd95970a5c559e8f00d60dfede /kernel/ptrace.c
parente3bd058f62896ec7a2c605ed62a0a811e9147947 (diff)
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: Tejun Heo <tj@kernel.org> Reported-by: Oleg Nesterov <oleg@redhat.com> Acked-by: Oleg Nesterov <oleg@redhat.com>
Diffstat (limited to 'kernel/ptrace.c')
-rw-r--r--kernel/ptrace.c59
1 files changed, 39 insertions, 20 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index e6098434b533..43485866749a 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -41,7 +41,26 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent)
41 * __ptrace_unlink - unlink ptracee and restore its execution state 41 * __ptrace_unlink - unlink ptracee and restore its execution state
42 * @child: ptracee to be unlinked 42 * @child: ptracee to be unlinked
43 * 43 *
44 * Remove @child from the ptrace list, move it back to the original parent. 44 * Remove @child from the ptrace list, move it back to the original parent,
45 * and restore the execution state so that it conforms to the group stop
46 * state.
47 *
48 * Unlinking can happen via two paths - explicit PTRACE_DETACH or ptracer
49 * exiting. For PTRACE_DETACH, unless the ptracee has been killed between
50 * ptrace_check_attach() and here, it's guaranteed to be in TASK_TRACED.
51 * If the ptracer is exiting, the ptracee can be in any state.
52 *
53 * After detach, the ptracee should be in a state which conforms to the
54 * group stop. If the group is stopped or in the process of stopping, the
55 * ptracee should be put into TASK_STOPPED; otherwise, it should be woken
56 * up from TASK_TRACED.
57 *
58 * If the ptracee is in TASK_TRACED and needs to be moved to TASK_STOPPED,
59 * it goes through TRACED -> RUNNING -> STOPPED transition which is similar
60 * to but in the opposite direction of what happens while attaching to a
61 * stopped task. However, in this direction, the intermediate RUNNING
62 * state is not hidden even from the current ptracer and if it immediately
63 * re-attaches and performs a WNOHANG wait(2), it may fail.
45 * 64 *
46 * CONTEXT: 65 * CONTEXT:
47 * write_lock_irq(tasklist_lock) 66 * write_lock_irq(tasklist_lock)
@@ -55,25 +74,25 @@ void __ptrace_unlink(struct task_struct *child)
55 list_del_init(&child->ptrace_entry); 74 list_del_init(&child->ptrace_entry);
56 75
57 spin_lock(&child->sighand->siglock); 76 spin_lock(&child->sighand->siglock);
58 if (task_is_traced(child)) { 77
59 /* 78 /*
60 * If group stop is completed or in progress, it should 79 * Reinstate GROUP_STOP_PENDING if group stop is in effect and
61 * participate in the group stop. Set GROUP_STOP_PENDING 80 * @child isn't dead.
62 * before kicking it. 81 */
63 * 82 if (!(child->flags & PF_EXITING) &&
64 * This involves TRACED -> RUNNING -> STOPPED transition 83 (child->signal->flags & SIGNAL_STOP_STOPPED ||
65 * which is similar to but in the opposite direction of 84 child->signal->group_stop_count))
66 * what happens while attaching to a stopped task. 85 child->group_stop |= GROUP_STOP_PENDING;
67 * However, in this direction, the intermediate RUNNING 86
68 * state is not hidden even from the current ptracer and if 87 /*
69 * it immediately re-attaches and performs a WNOHANG 88 * If transition to TASK_STOPPED is pending or in TASK_TRACED, kick
70 * wait(2), it may fail. 89 * @child in the butt. Note that @resume should be used iff @child
71 */ 90 * is in TASK_TRACED; otherwise, we might unduly disrupt
72 if (child->signal->flags & SIGNAL_STOP_STOPPED || 91 * TASK_KILLABLE sleeps.
73 child->signal->group_stop_count) 92 */
74 child->group_stop |= GROUP_STOP_PENDING; 93 if (child->group_stop & GROUP_STOP_PENDING || task_is_traced(child))
75 signal_wake_up(child, 1); 94 signal_wake_up(child, task_is_traced(child));
76 } 95
77 spin_unlock(&child->sighand->siglock); 96 spin_unlock(&child->sighand->siglock);
78} 97}
79 98