| From 592031cc10858be4adb10f6c0f2608f6f21824aa Mon Sep 17 00:00:00 2001 |
| From: "Paul E. McKenney" <paulmck@kernel.org> |
| Date: Tue, 15 Sep 2020 14:03:34 -0700 |
| Subject: rcu-tasks: Fix low-probability task_struct leak |
| |
| From: Paul E. McKenney <paulmck@kernel.org> |
| |
| commit 592031cc10858be4adb10f6c0f2608f6f21824aa upstream. |
| |
| When rcu_tasks_trace_postgp() function detects an RCU Tasks Trace |
| CPU stall, it adds all tasks blocking the current grace period to |
| a list, invoking get_task_struct() on each to prevent them from |
| being freed while on the list. It then traverses that list, |
| printing stall-warning messages for each one that is still blocking |
| the current grace period and removing it from the list. The list |
| removal invokes the matching put_task_struct(). |
| |
| This of course means that in the admittedly unlikely event that some |
| task executes its outermost rcu_read_unlock_trace() in the meantime, it |
| won't be removed from the list and put_task_struct() won't be executing, |
| resulting in a task_struct leak. This commit therefore makes the list |
| removal and put_task_struct() unconditional, stopping the leak. |
| |
| Note further that this bug can occur only after an RCU Tasks Trace CPU |
| stall warning, which by default only happens after a grace period has |
| extended for ten minutes (yes, not a typo, minutes). |
| |
| Fixes: 4593e772b502 ("rcu-tasks: Add stall warnings for RCU Tasks Trace") |
| Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com> |
| Cc: Daniel Borkmann <daniel@iogearbox.net> |
| Cc: Jiri Olsa <jolsa@redhat.com> |
| Cc: <bpf@vger.kernel.org> |
| Cc: <stable@vger.kernel.org> # 5.7.x |
| Signed-off-by: Paul E. McKenney <paulmck@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| kernel/rcu/tasks.h | 8 ++++---- |
| 1 file changed, 4 insertions(+), 4 deletions(-) |
| |
| --- a/kernel/rcu/tasks.h |
| +++ b/kernel/rcu/tasks.h |
| @@ -1082,11 +1082,11 @@ static void rcu_tasks_trace_postgp(struc |
| if (READ_ONCE(t->trc_reader_special.b.need_qs)) |
| trc_add_holdout(t, &holdouts); |
| firstreport = true; |
| - list_for_each_entry_safe(t, g, &holdouts, trc_holdout_list) |
| - if (READ_ONCE(t->trc_reader_special.b.need_qs)) { |
| + list_for_each_entry_safe(t, g, &holdouts, trc_holdout_list) { |
| + if (READ_ONCE(t->trc_reader_special.b.need_qs)) |
| show_stalled_task_trace(t, &firstreport); |
| - trc_del_holdout(t); |
| - } |
| + trc_del_holdout(t); // Release task_struct reference. |
| + } |
| if (firstreport) |
| pr_err("INFO: rcu_tasks_trace detected stalls? (Counter/taskslist mismatch?)\n"); |
| show_stalled_ipi_trace(); |