| From 1bd263cbd3951f8f36ee6dcfe9160dafcfdd91fe Mon Sep 17 00:00:00 2001 |
| From: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| Date: Thu, 29 Aug 2013 18:21:04 +0200 |
| Subject: [PATCH] ptrace: fix ptrace vs tasklist_lock race |
| |
| As explained by Alexander Fyodorov <halcy@yandex.ru>: |
| |
| |read_lock(&tasklist_lock) in ptrace_stop() is converted to mutex on RT kernel, |
| |and it can remove __TASK_TRACED from task->state (by moving it to |
| |task->saved_state). If parent does wait() on child followed by a sys_ptrace |
| |call, the following race can happen: |
| | |
| |- child sets __TASK_TRACED in ptrace_stop() |
| |- parent does wait() which eventually calls wait_task_stopped() and returns |
| | child's pid |
| |- child blocks on read_lock(&tasklist_lock) in ptrace_stop() and moves |
| | __TASK_TRACED flag to saved_state |
| |- parent calls sys_ptrace, which calls ptrace_check_attach() and wait_task_inactive() |
| |
| The patch is based on his initial patch where an additional check is |
| added in case the __TASK_TRACED moved to ->saved_state. The pi_lock is |
| taken in case the caller is interrupted between looking into ->state and |
| ->saved_state. |
| |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| --- |
| include/linux/sched.h | 19 ++++++++++++++++++- |
| 1 file changed, 18 insertions(+), 1 deletion(-) |
| |
| diff --git a/include/linux/sched.h b/include/linux/sched.h |
| index e0a05de..bd60b9d 100644 |
| --- a/include/linux/sched.h |
| +++ b/include/linux/sched.h |
| @@ -175,7 +175,6 @@ extern char ___assert_task_state[1 - 2*!!( |
| TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \ |
| __TASK_TRACED) |
| |
| -#define task_is_traced(task) ((task->state & __TASK_TRACED) != 0) |
| #define task_is_stopped(task) ((task->state & __TASK_STOPPED) != 0) |
| #define task_is_dead(task) ((task)->exit_state != 0) |
| #define task_is_stopped_or_traced(task) \ |
| @@ -2532,6 +2531,24 @@ static inline int signal_pending_state(long state, struct task_struct *p) |
| return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p); |
| } |
| |
| +static inline bool task_is_traced(struct task_struct *task) |
| +{ |
| + bool traced = false; |
| + |
| + if (task->state & __TASK_TRACED) |
| + return true; |
| +#ifdef CONFIG_PREEMPT_RT_FULL |
| + /* in case the task is sleeping on tasklist_lock */ |
| + raw_spin_lock_irq(&task->pi_lock); |
| + if (task->state & __TASK_TRACED) |
| + traced = true; |
| + else if (task->saved_state & __TASK_TRACED) |
| + traced = true; |
| + raw_spin_unlock_irq(&task->pi_lock); |
| +#endif |
| + return traced; |
| +} |
| + |
| /* |
| * cond_resched() and cond_resched_lock(): latency reduction via |
| * explicit rescheduling in places that are safe. The return |
| -- |
| 1.8.4.rc3 |
| |