Commit cece79ae authored by Roland McGrath's avatar Roland McGrath Committed by Linus Torvalds

[PATCH] cleanup ptrace stops and remove notify_parent

This adds a new state TASK_TRACED that is used in place of TASK_STOPPED
when a thread stops because it is ptraced.  Now ptrace operations are only
permitted when the target is in TASK_TRACED state, not in TASK_STOPPED. 
This means that if a process is stopped normally by a job control signal
and then you PTRACE_ATTACH to it, you will have to send it a SIGCONT before
you can do any ptrace operations on it.  (The SIGCONT will be reported to
ptrace and then you can discard it instead of passing it through when you
call PTRACE_CONT et al.)

If a traced child gets orphaned while in TASK_TRACED state, it morphs into
TASK_STOPPED state.  This makes it again possible to resume or destroy the
process with SIGCONT or SIGKILL.

All non-signal tracing stops should now be done via ptrace_notify.  I've
updated the syscall tracing code in several architectures to do this
instead of replicating the work by hand.  I also fixed several that were
unnecessarily repeating some of the checks in ptrace_check_attach.  Calling
ptrace_check_attach alone is sufficient, and the old checks repeated before
are now incorrect, not just superfluous.

I've closed a race in ptrace_check_attach.  With this, we should have a
robust guarantee that when ptrace starts operating, the task will be in
TASK_TRACED state and won't come out of it.  This is because the only way
to resume from TASK_TRACED is via ptrace operations, and only the one
parent thread attached as the tracer can do those.

This patch also cleans up the do_notify_parent and do_notify_parent_cldstop
code so that the dead and stopped cases are completely disjoint.  The
notify_parent function is gone.
Signed-off-by: default avatarRoland McGrath <roland@redhat.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent cfec5c01
......@@ -792,11 +792,8 @@ asmlinkage void syscall_trace(int why, struct pt_regs *regs)
/* the 0x80 provides a way for the tracing parent to distinguish
between a syscall stop and SIGTRAP delivery */
current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -729,11 +729,8 @@ asmlinkage void syscall_trace(int why, struct pt_regs *regs)
/* the 0x80 provides a way for the tracing parent to distinguish
between a syscall stop and SIGTRAP delivery */
current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -85,17 +85,8 @@ sys_ptrace(long request, long pid, long addr, long data)
goto out_tsk;
}
ret = -ESRCH;
if (!(child->ptrace & PT_PTRACED))
goto out_tsk;
if (child->state != TASK_STOPPED) {
if (request != PTRACE_KILL)
goto out_tsk;
}
if (child->parent != current)
ret = ptrace_check_attach(child, request == PTRACE_KILL);
if (ret < 0)
goto out_tsk;
switch (request) {
......
......@@ -89,13 +89,6 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
ret = ptrace_attach(child);
goto out_tsk;
}
ret = -ESRCH;
if (!(child->ptrace & PT_PTRACED))
goto out_tsk;
if (child->state != TASK_STOPPED) {
if (request != PTRACE_KILL)
goto out_tsk;
}
ret = ptrace_check_attach(child, request == PTRACE_KILL);
if (ret < 0)
goto out_tsk;
......@@ -270,10 +263,8 @@ asmlinkage void syscall_trace(void)
return;
if (!(current->ptrace & PT_PTRACED))
return;
current->exit_code = SIGTRAP;
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -379,11 +379,8 @@ asmlinkage void syscall_trace(void)
if (!current->thread.work.delayed_trace &&
!current->thread.work.syscall_trace)
return;
current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -133,13 +133,6 @@ asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
ret = ptrace_attach(child);
goto out_tsk;
}
ret = -ESRCH;
if (!(child->ptrace & PT_PTRACED))
goto out_tsk;
if (child->state != TASK_STOPPED) {
if (request != PTRACE_KILL)
goto out_tsk;
}
ret = ptrace_check_attach(child, request == PTRACE_KILL);
if (ret < 0)
goto out_tsk;
......@@ -376,10 +369,8 @@ asmlinkage void syscall_trace(void)
return;
if (!(current->ptrace & PT_PTRACED))
return;
current->exit_code = SIGTRAP;
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -404,11 +404,8 @@ void syscall_trace(void)
return;
if (!(current->ptrace & PT_PTRACED))
return;
current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -311,11 +311,8 @@ asmlinkage void syscall_trace(void)
if (!(tsk->ptrace & PT_PTRACED))
return;
tsk->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
tsk->state = TASK_STOPPED;
notify_parent(tsk, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -614,12 +614,9 @@ asmlinkage void syscall_trace(void)
return;
if (!(current->ptrace & PT_PTRACED))
return;
current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
current->state = TASK_STOPPED;
current->thread.flags ^= MAGIC_CONSTANT;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -627,11 +627,8 @@ asmlinkage void syscall_trace(void)
return;
if (!(current->ptrace & PT_PTRACED))
return;
current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
......
......@@ -147,14 +147,8 @@ int sys_ptrace(long request, long pid, long addr, long data)
rval = ptrace_attach(child);
goto out_tsk;
}
rval = -ESRCH;
if (!(child->ptrace & PT_PTRACED))
goto out_tsk;
if (child->state != TASK_STOPPED) {
if (request != PTRACE_KILL)
goto out_tsk;
}
if (child->parent != current)
ret = ptrace_check_attach(child, request == PTRACE_KILL);
if (ret < 0)
goto out_tsk;
switch (request) {
......@@ -269,11 +263,8 @@ asmlinkage void syscall_trace(void)
return;
/* The 0x80 provides a way for the tracing parent to distinguish
between a syscall stop and SIGTRAP delivery */
current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0);
current->state = TASK_STOPPED;
notify_parent(current, SIGCHLD);
schedule();
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
......
......@@ -130,8 +130,9 @@ static const char *task_state_array[] = {
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"Z (zombie)", /* 8 */
"X (dead)" /* 16 */
"T (tracing stop)", /* 8 */
"Z (zombie)", /* 16 */
"X (dead)" /* 32 */
};
static inline const char * get_task_state(struct task_struct *tsk)
......@@ -141,7 +142,8 @@ static inline const char * get_task_state(struct task_struct *tsk)
TASK_UNINTERRUPTIBLE |
TASK_ZOMBIE |
TASK_DEAD |
TASK_STOPPED);
TASK_STOPPED |
TASK_TRACED);
const char **p = &task_state_array[0];
while (state) {
......
......@@ -287,7 +287,8 @@ static int proc_root_link(struct inode *inode, struct dentry **dentry, struct vf
#define MAY_PTRACE(task) \
(task == current || \
(task->parent == current && \
(task->ptrace & PT_PTRACED) && task->state == TASK_STOPPED && \
(task->ptrace & PT_PTRACED) && \
(task->state == TASK_STOPPED || task->state == TASK_TRACED) && \
security_ptrace(current,task) == 0))
static int may_ptrace_attach(struct task_struct *task)
......
......@@ -106,8 +106,9 @@ extern unsigned long nr_iowait(void);
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_STOPPED 4
#define TASK_ZOMBIE 8
#define TASK_DEAD 16
#define TASK_TRACED 8
#define TASK_ZOMBIE 16
#define TASK_DEAD 32
#define __set_task_state(tsk, state_value) \
do { (tsk)->state = (state_value); } while (0)
......@@ -738,7 +739,6 @@ extern int __kill_pg_info(int sig, struct siginfo *info, pid_t pgrp);
extern int kill_pg_info(int, struct siginfo *, pid_t);
extern int kill_sl_info(int, struct siginfo *, pid_t);
extern int kill_proc_info(int, struct siginfo *, pid_t);
extern void notify_parent(struct task_struct *, int);
extern void do_notify_parent(struct task_struct *, int);
extern void force_sig(int, struct task_struct *);
extern void force_sig_specific(int, struct task_struct *);
......
......@@ -555,6 +555,14 @@ static inline void reparent_thread(task_t *p, task_t *father, int traced)
if (p->state == TASK_ZOMBIE && p->exit_signal != -1 &&
thread_group_empty(p))
do_notify_parent(p, p->exit_signal);
else if (p->state == TASK_TRACED) {
/*
* If it was at a trace stop, turn it into
* a normal stop since it's no longer being
* traced.
*/
p->state = TASK_STOPPED;
}
}
/*
......@@ -1164,7 +1172,7 @@ static int wait_task_stopped(task_t *p, int delayed_group_leader, int noreap,
* race with the TASK_ZOMBIE case.
*/
exit_code = xchg(&p->exit_code, 0);
if (unlikely(p->state > TASK_STOPPED)) {
if (unlikely(p->state >= TASK_ZOMBIE)) {
/*
* The task resumed and then died. Let the next iteration
* catch it in TASK_ZOMBIE. Note that exit_code might
......@@ -1245,6 +1253,10 @@ static long do_wait(pid_t pid, int options, struct siginfo __user *infop,
flag = 1;
switch (p->state) {
case TASK_TRACED:
if (!(p->ptrace & PT_PTRACED))
continue;
/*FALLTHROUGH*/
case TASK_STOPPED:
if (!(options & WUNTRACED) &&
!(p->ptrace & PT_PTRACED))
......
......@@ -25,7 +25,8 @@ static inline int freezeable(struct task_struct * p)
(p->flags & PF_NOFREEZE) ||
(p->state == TASK_ZOMBIE) ||
(p->state == TASK_DEAD) ||
(p->state == TASK_STOPPED))
(p->state == TASK_STOPPED) ||
(p->state == TASK_TRACED))
return 0;
return 1;
}
......@@ -70,6 +71,7 @@ int freeze_processes(void)
if (!freezeable(p))
continue;
if ((p->flags & PF_FROZEN) ||
(p->state == TASK_TRACED) ||
(p->state == TASK_STOPPED))
continue;
......
......@@ -55,6 +55,15 @@ void __ptrace_unlink(task_t *child)
REMOVE_LINKS(child);
child->parent = child->real_parent;
SET_LINKS(child);
if (child->state == TASK_TRACED) {
/*
* Turn a tracing stop into a normal stop now,
* since with no tracer there would be no way
* to wake it up with SIGCONT or SIGKILL.
*/
child->state = TASK_STOPPED;
}
}
/*
......@@ -62,20 +71,28 @@ void __ptrace_unlink(task_t *child)
*/
int ptrace_check_attach(struct task_struct *child, int kill)
{
if (!(child->ptrace & PT_PTRACED))
return -ESRCH;
int ret = -ESRCH;
if (child->parent != current)
return -ESRCH;
/*
* We take the read lock around doing both checks to close a
* possible race where someone else was tracing our child and
* detached between these two checks. After this locked check,
* 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.
*/
read_lock(&tasklist_lock);
if ((child->ptrace & PT_PTRACED) && child->parent == current)
ret = 0;
read_unlock(&tasklist_lock);
if (!kill) {
if (child->state != TASK_STOPPED)
if (!ret && !kill) {
if (child->state != TASK_TRACED)
return -ESRCH;
wait_task_inactive(child);
}
/* All systems go.. */
return 0;
return ret;
}
int ptrace_attach(struct task_struct *task)
......@@ -281,15 +298,13 @@ static int ptrace_setoptions(struct task_struct *child, long data)
static int ptrace_getsiginfo(struct task_struct *child, siginfo_t __user * data)
{
if (child->last_siginfo == NULL)
return -EINVAL;
BUG_ON(child->last_siginfo == NULL);
return copy_siginfo_to_user(data, child->last_siginfo);
}
static int ptrace_setsiginfo(struct task_struct *child, siginfo_t __user * data)
{
if (child->last_siginfo == NULL)
return -EINVAL;
BUG_ON(child->last_siginfo == NULL);
if (copy_from_user(child->last_siginfo, data, sizeof (siginfo_t)) != 0)
return -EFAULT;
return 0;
......@@ -322,24 +337,3 @@ int ptrace_request(struct task_struct *child, long request,
return ret;
}
void ptrace_notify(int exit_code)
{
BUG_ON (!(current->ptrace & PT_PTRACED));
/* Let the debugger run. */
current->exit_code = exit_code;
set_current_state(TASK_STOPPED);
notify_parent(current, SIGCHLD);
schedule();
/*
* Signals sent while we were stopped might set TIF_SIGPENDING.
*/
spin_lock_irq(&current->sighand->siglock);
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
}
EXPORT_SYMBOL(ptrace_notify);
......@@ -1258,7 +1258,7 @@ static int try_to_wake_up(task_t * p, unsigned int state, int sync)
int fastcall wake_up_process(task_t * p)
{
return try_to_wake_up(p, TASK_STOPPED |
return try_to_wake_up(p, TASK_STOPPED | TASK_TRACED |
TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE, 0);
}
......@@ -3679,7 +3679,7 @@ static void show_task(task_t * p)
task_t *relative;
unsigned state;
unsigned long free = 0;
static const char *stat_nam[] = { "R", "S", "D", "T", "Z", "W" };
static const char *stat_nam[] = { "R", "S", "D", "T", "t", "Z", "X" };
printk("%-13.13s ", p->comm);
state = p->state ? __ffs(p->state) + 1 : 0;
......
This diff is collapsed.
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