From: Frederic Weisbecker <frederic@kernel.org>
To: LKML <linux-kernel@vger.kernel.org>
Cc: Frederic Weisbecker <frederic@kernel.org>,
Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>
Subject: [PATCH 4/4] perf: Fix event leak upon exec and file release
Date: Sat, 30 Mar 2024 00:58:12 +0100 [thread overview]
Message-ID: <20240329235812.18917-5-frederic@kernel.org> (raw)
In-Reply-To: <20240329235812.18917-1-frederic@kernel.org>
The perf pending task work is never waited upon the matching event
release. In the case of a child event, released via free_event()
directly, this can potentially result in a leaked event, such as in the
following scenario that doesn't even require a weak IRQ work
implementation to trigger:
schedule()
prepare_task_switch()
=======> <NMI>
perf_event_overflow()
event->pending_sigtrap = ...
irq_work_queue(&event->pending_irq)
<======= </NMI>
perf_event_task_sched_out()
event_sched_out()
event->pending_sigtrap = 0;
atomic_long_inc_not_zero(&event->refcount)
task_work_add(&event->pending_task)
finish_lock_switch()
=======> <IRQ>
perf_pending_irq()
//do nothing, rely on pending task work
<======= </IRQ>
begin_new_exec()
perf_event_exit_task()
perf_event_exit_event()
// If is child event
free_event()
WARN(atomic_long_cmpxchg(&event->refcount, 1, 0) != 1)
// event is leaked
Similar scenarios can also happen with perf_event_remove_on_exec() or
simply against concurrent perf_event_release().
Fix this with synchonizing against the possibly remaining pending task
work while freeing the event, just like is done with remaining pending
IRQ work. This means that the pending task callback neither need nor
should hold a reference to the event, preventing it from ever beeing
freed.
Fixes: 517e6a301f34 ("perf: Fix perf_pending_task() UaF")
Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
---
include/linux/perf_event.h | 1 +
kernel/events/core.c | 33 +++++++++++++++++++++++++++++----
2 files changed, 30 insertions(+), 4 deletions(-)
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index d2a15c0c6f8a..2f2e222e1003 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -786,6 +786,7 @@ struct perf_event {
struct irq_work pending_irq;
struct callback_head pending_task;
unsigned int pending_work;
+ wait_queue_head_t pending_work_wq;
atomic_t event_limit;
diff --git a/kernel/events/core.c b/kernel/events/core.c
index c1632e69c69d..588e35848a4e 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -2290,7 +2290,6 @@ event_sched_out(struct perf_event *event, struct perf_event_context *ctx)
if (state != PERF_EVENT_STATE_OFF &&
!event->pending_work) {
if (task_work_add(current, &event->pending_task, TWA_RESUME) >= 0) {
- WARN_ON_ONCE(!atomic_long_inc_not_zero(&event->refcount));
dec = false;
event->pending_work = 1;
}
@@ -5188,9 +5187,35 @@ static bool exclusive_event_installable(struct perf_event *event,
static void perf_addr_filters_splice(struct perf_event *event,
struct list_head *head);
+static void perf_pending_task_sync(struct perf_event *event)
+{
+ struct callback_head *head = &event->pending_task;
+
+ if (!event->pending_work)
+ return;
+ /*
+ * If the task is queued to the current task's queue, we
+ * obviously can't wait for it to complete. Simply cancel it.
+ */
+ if (task_work_cancel(current, head)) {
+ event->pending_work = 0;
+ local_dec(&event->ctx->nr_pending);
+ return;
+ }
+
+ /*
+ * All accesses related to the event are within the same
+ * non-preemptible section in perf_pending_task(). The RCU
+ * grace period before the event is freed will make sure all
+ * those accesses are complete by then.
+ */
+ wait_event(event->pending_work_wq, !event->pending_work);
+}
+
static void _free_event(struct perf_event *event)
{
irq_work_sync(&event->pending_irq);
+ perf_pending_task_sync(event);
unaccount_event(event);
@@ -6808,24 +6833,23 @@ static void perf_pending_task(struct callback_head *head)
struct perf_event *event = container_of(head, struct perf_event, pending_task);
int rctx;
+ preempt_disable_notrace();
/*
* If we 'fail' here, that's OK, it means recursion is already disabled
* and we won't recurse 'further'.
*/
- preempt_disable_notrace();
rctx = perf_swevent_get_recursion_context();
if (event->pending_work) {
event->pending_work = 0;
perf_sigtrap(event);
local_dec(&event->ctx->nr_pending);
+ wake_up(&event->pending_work_wq);
}
if (rctx >= 0)
perf_swevent_put_recursion_context(rctx);
preempt_enable_notrace();
-
- put_event(event);
}
#ifdef CONFIG_GUEST_PERF_EVENTS
@@ -11933,6 +11957,7 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
init_waitqueue_head(&event->waitq);
init_irq_work(&event->pending_irq, perf_pending_irq);
init_task_work(&event->pending_task, perf_pending_task);
+ init_waitqueue_head(&event->pending_work_wq);
mutex_init(&event->mmap_mutex);
raw_spin_lock_init(&event->addr_filters.lock);
--
2.44.0
next prev parent reply other threads:[~2024-03-29 23:58 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-29 23:58 [PATCH 0/4] perf: Fix leaked events when sigtrap = 1 Frederic Weisbecker
2024-03-29 23:58 ` [PATCH 1/4] task_work: s/task_work_cancel()/task_work_cancel_func()/ Frederic Weisbecker
2024-03-29 23:58 ` [PATCH 2/4] task_work: Introduce task_work_cancel() again Frederic Weisbecker
2024-03-30 21:10 ` kernel test robot
2024-03-29 23:58 ` [PATCH 3/4] perf: Fix event leak upon exit Frederic Weisbecker
2024-03-29 23:58 ` Frederic Weisbecker [this message]
2024-03-30 3:23 ` [PATCH 0/4] perf: Fix leaked events when sigtrap = 1 Ian Rogers
2024-04-08 19:43 ` Frederic Weisbecker
2024-04-08 20:20 ` Arnaldo Carvalho de Melo
2024-04-08 20:58 ` Frederic Weisbecker
-- strict thread matches above, loose matches on Subject: below --
2024-05-15 14:43 [PATCH 0/4 v2] perf: Fix leaked sigtrap events Frederic Weisbecker
2024-05-15 14:43 ` [PATCH 4/4] perf: Fix event leak upon exec and file release Frederic Weisbecker
2024-05-16 9:41 ` Peter Zijlstra
2024-05-16 14:09 [PATCH 0/4 v3] perf: Fix leaked sigtrap events Frederic Weisbecker
2024-05-16 14:09 ` [PATCH 4/4] perf: Fix event leak upon exec and file release Frederic Weisbecker
2024-06-21 9:15 [PATCH 0/4 v4] perf: Fix leaked sigtrap events Frederic Weisbecker
2024-06-21 9:16 ` [PATCH 4/4] perf: Fix event leak upon exec and file release Frederic Weisbecker
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240329235812.18917-5-frederic@kernel.org \
--to=frederic@kernel.org \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alexander.shishkin@linux.intel.com \
--cc=irogers@google.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox