diff options
Diffstat (limited to 'debian/patches-rt/0101-ptrace-fix-ptrace-vs-tasklist_lock-race.patch')
-rw-r--r-- | debian/patches-rt/0101-ptrace-fix-ptrace-vs-tasklist_lock-race.patch | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/debian/patches-rt/0101-ptrace-fix-ptrace-vs-tasklist_lock-race.patch b/debian/patches-rt/0101-ptrace-fix-ptrace-vs-tasklist_lock-race.patch new file mode 100644 index 000000000..434ebc046 --- /dev/null +++ b/debian/patches-rt/0101-ptrace-fix-ptrace-vs-tasklist_lock-race.patch @@ -0,0 +1,222 @@ +From dfa115d012ba4d7eca9ed325a4a7327ce885e300 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 101/158] ptrace: fix ptrace vs tasklist_lock race +Origin: https://www.kernel.org/pub/linux/kernel/projects/rt/5.15/older/patches-5.15.10-rt24.tar.xz + +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. + +[ Fix for ptrace_unfreeze_traced() by Oleg Nesterov ] +Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> +Signed-off-by: Thomas Gleixner <tglx@linutronix.de> +--- + include/linux/sched.h | 79 ++++++++++++++++++++++++++++++++++++++++--- + kernel/ptrace.c | 38 +++++++++++++++++---- + kernel/sched/core.c | 4 +-- + 3 files changed, 108 insertions(+), 13 deletions(-) + +diff --git a/include/linux/sched.h b/include/linux/sched.h +index 2b8c8150792c..04172b57d71d 100644 +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -118,12 +118,8 @@ struct task_group; + + #define task_is_running(task) (READ_ONCE((task)->__state) == TASK_RUNNING) + +-#define task_is_traced(task) ((READ_ONCE(task->__state) & __TASK_TRACED) != 0) +- + #define task_is_stopped(task) ((READ_ONCE(task->__state) & __TASK_STOPPED) != 0) + +-#define task_is_stopped_or_traced(task) ((READ_ONCE(task->__state) & (__TASK_STOPPED | __TASK_TRACED)) != 0) +- + /* + * Special states are those that do not use the normal wait-loop pattern. See + * the comment with set_special_state(). +@@ -2015,6 +2011,81 @@ static inline int test_tsk_need_resched(struct task_struct *tsk) + return unlikely(test_tsk_thread_flag(tsk,TIF_NEED_RESCHED)); + } + ++#ifdef CONFIG_PREEMPT_RT ++static inline bool task_match_saved_state(struct task_struct *p, long match_state) ++{ ++ return p->saved_state == match_state; ++} ++ ++static inline bool task_is_traced(struct task_struct *task) ++{ ++ bool traced = false; ++ ++ /* in case the task is sleeping on tasklist_lock */ ++ raw_spin_lock_irq(&task->pi_lock); ++ if (READ_ONCE(task->__state) & __TASK_TRACED) ++ traced = true; ++ else if (task->saved_state & __TASK_TRACED) ++ traced = true; ++ raw_spin_unlock_irq(&task->pi_lock); ++ return traced; ++} ++ ++static inline bool task_is_stopped_or_traced(struct task_struct *task) ++{ ++ bool traced_stopped = false; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&task->pi_lock, flags); ++ ++ if (READ_ONCE(task->__state) & (__TASK_STOPPED | __TASK_TRACED)) ++ traced_stopped = true; ++ else if (task->saved_state & (__TASK_STOPPED | __TASK_TRACED)) ++ traced_stopped = true; ++ ++ raw_spin_unlock_irqrestore(&task->pi_lock, flags); ++ return traced_stopped; ++} ++ ++#else ++ ++static inline bool task_match_saved_state(struct task_struct *p, long match_state) ++{ ++ return false; ++} ++ ++static inline bool task_is_traced(struct task_struct *task) ++{ ++ return READ_ONCE(task->__state) & __TASK_TRACED; ++} ++ ++static inline bool task_is_stopped_or_traced(struct task_struct *task) ++{ ++ return READ_ONCE(task->__state) & (__TASK_STOPPED | __TASK_TRACED); ++} ++#endif ++ ++static inline bool task_match_state_or_saved(struct task_struct *p, ++ long match_state) ++{ ++ if (READ_ONCE(p->__state) == match_state) ++ return true; ++ ++ return task_match_saved_state(p, match_state); ++} ++ ++static inline bool task_match_state_lock(struct task_struct *p, ++ long match_state) ++{ ++ bool match; ++ ++ raw_spin_lock_irq(&p->pi_lock); ++ match = task_match_state_or_saved(p, match_state); ++ raw_spin_unlock_irq(&p->pi_lock); ++ ++ return match; ++} ++ + /* + * cond_resched() and cond_resched_lock(): latency reduction via + * explicit rescheduling in places that are safe. The return +diff --git a/kernel/ptrace.c b/kernel/ptrace.c +index f8589bf8d7dc..df08e8e64a83 100644 +--- a/kernel/ptrace.c ++++ b/kernel/ptrace.c +@@ -197,7 +197,18 @@ static bool ptrace_freeze_traced(struct task_struct *task) + spin_lock_irq(&task->sighand->siglock); + if (task_is_traced(task) && !looks_like_a_spurious_pid(task) && + !__fatal_signal_pending(task)) { ++#ifdef CONFIG_PREEMPT_RT ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&task->pi_lock, flags); ++ if (READ_ONCE(task->__state) & __TASK_TRACED) ++ WRITE_ONCE(task->__state, __TASK_TRACED); ++ else ++ task->saved_state = __TASK_TRACED; ++ raw_spin_unlock_irqrestore(&task->pi_lock, flags); ++#else + WRITE_ONCE(task->__state, __TASK_TRACED); ++#endif + ret = true; + } + spin_unlock_irq(&task->sighand->siglock); +@@ -207,7 +218,11 @@ static bool ptrace_freeze_traced(struct task_struct *task) + + static void ptrace_unfreeze_traced(struct task_struct *task) + { +- if (READ_ONCE(task->__state) != __TASK_TRACED) ++ unsigned long flags; ++ bool frozen = true; ++ ++ if (!IS_ENABLED(CONFIG_PREEMPT_RT) && ++ READ_ONCE(task->__state) != __TASK_TRACED) + return; + + WARN_ON(!task->ptrace || task->parent != current); +@@ -217,12 +232,21 @@ static void ptrace_unfreeze_traced(struct task_struct *task) + * Recheck state under the lock to close this race. + */ + spin_lock_irq(&task->sighand->siglock); +- if (READ_ONCE(task->__state) == __TASK_TRACED) { +- if (__fatal_signal_pending(task)) +- wake_up_state(task, __TASK_TRACED); +- else +- WRITE_ONCE(task->__state, TASK_TRACED); +- } ++ raw_spin_lock_irqsave(&task->pi_lock, flags); ++ if (READ_ONCE(task->__state) == __TASK_TRACED) ++ WRITE_ONCE(task->__state, TASK_TRACED); ++ ++#ifdef CONFIG_PREEMPT_RT ++ else if (task->saved_state == __TASK_TRACED) ++ task->saved_state = TASK_TRACED; ++#endif ++ else ++ frozen = false; ++ raw_spin_unlock_irqrestore(&task->pi_lock, flags); ++ ++ if (frozen && __fatal_signal_pending(task)) ++ wake_up_state(task, __TASK_TRACED); ++ + spin_unlock_irq(&task->sighand->siglock); + } + +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +index 2d7cf80d95d9..9ff7e8bd058a 100644 +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -3207,7 +3207,7 @@ unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state + * is actually now running somewhere else! + */ + while (task_running(rq, p)) { +- if (match_state && unlikely(READ_ONCE(p->__state) != match_state)) ++ if (match_state && !task_match_state_lock(p, match_state)) + return 0; + cpu_relax(); + } +@@ -3222,7 +3222,7 @@ unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state + running = task_running(rq, p); + queued = task_on_rq_queued(p); + ncsw = 0; +- if (!match_state || READ_ONCE(p->__state) == match_state) ++ if (!match_state || task_match_state_or_saved(p, match_state)) + ncsw = p->nvcsw | LONG_MIN; /* sets MSB */ + task_rq_unlock(rq, p, &rf); + +-- +2.33.1 + |