diff options
author | Arnaldo Carvalho de Melo <acme@redhat.com> | 2019-07-05 18:11:35 +0300 |
---|---|---|
committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2019-07-06 20:29:32 +0300 |
commit | 4c00af0e94cd01b8c5a5e6b3323d34677b04e192 (patch) | |
tree | 8a95cfb4207758bf4b88ae00da515f822df78b81 /tools/perf/util/thread.c | |
parent | c952b35f4b15dd1b83e952718dec3307256383ef (diff) | |
download | linux-4c00af0e94cd01b8c5a5e6b3323d34677b04e192.tar.xz |
perf thread: Allow references to thread objects after machine__exit()
Threads are created when we either synthesize PERF_RECORD_FORK events
for pre-existing threads or when we receive PERF_RECORD_FORK events from
the kernel as new threads get created.
We then keep them in machine->threads[].entries rb trees till when we
receive a PERF_RECORD_EXIT, i.e. that thread terminated.
The thread object has a reference count that is grabbed when, for
instance, we keep that thread referenced in struct hist_entry, in 'perf
report' and 'perf top'.
When we receive a PERF_RECORD_EXIT we remove the thread object from the
rb tree and move it to the corresponding machine->threads[].dead list,
then we do a thread__put(), dropping the reference we had for keeping it
in the rb tree.
In thread__put() we were assuming that when the reference count hit zero
we should remove it from the dead list by simply doing a
list_del_init(&thread->node).
That works well when all the thread lifetime is during the machine that
has the list heads lifetime, since we know that we can do the
list_del_init() and it will update the 'dead' list_head.
But in 'perf sched lat' we were doing:
machine__new() (via perf_session__new)
process events, grabbing refcounts to keep those thread objects
in 'perf sched' local data structures.
machine__exit() (via perf_session__delete) which would delete the
'dead' list heads.
And then doing the final thread__put() for the refcounts 'perf sched'
rightfully obtained for keeping those thread object references.
b00m, since thread__put() would do the list_del_init() touching
a dead dead list head.
Fix it by removing all the dead threads from machine->threads[].dead at
machine__exit(), since whatever is there should have refcounts taken by
things like 'perf sched lat', and make thread__put() check if the thread
is in a linked list before removing it from that list.
Reported-by: Wei Li <liwei391@huawei.com>
Link: https://lkml.kernel.org/r/20190508143648.8153-1-liwei391@huawei.com
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Zhipeng Xie <xiezhipeng1@huawei.com>
Link: https://lkml.kernel.org/r/20190704194355.GI10740@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/util/thread.c')
-rw-r--r-- | tools/perf/util/thread.c | 23 |
1 files changed, 20 insertions, 3 deletions
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index b413ba5b9835..7bfb740d2ede 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -125,10 +125,27 @@ void thread__put(struct thread *thread) { if (thread && refcount_dec_and_test(&thread->refcnt)) { /* - * Remove it from the dead_threads list, as last reference - * is gone. + * Remove it from the dead threads list, as last reference is + * gone, if it is in a dead threads list. + * + * We may not be there anymore if say, the machine where it was + * stored was already deleted, so we already removed it from + * the dead threads and some other piece of code still keeps a + * reference. + * + * This is what 'perf sched' does and finally drops it in + * perf_sched__lat(), where it calls perf_sched__read_events(), + * that processes the events by creating a session and deleting + * it, which ends up destroying the list heads for the dead + * threads, but before it does that it removes all threads from + * it using list_del_init(). + * + * So we need to check here if it is in a dead threads list and + * if so, remove it before finally deleting the thread, to avoid + * an use after free situation. */ - list_del_init(&thread->node); + if (!list_empty(&thread->node)) + list_del_init(&thread->node); thread__delete(thread); } } |