mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 11:03:07 -04:00
Merge tag 'perf_urgent_for_v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull perf fixes from Borislav Petkov: - Prevent the use of exited events as group leaders - Avoid use-after-free of an event's group leader by promoting detached sibling events to standalone entities and correct related accounting and state transitions * tag 'perf_urgent_for_v7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: perf/core: Fix group leader use-after-free after sibling detach perf: Reject exited events as group leaders
This commit is contained in:
@@ -2343,6 +2343,34 @@ static inline struct list_head *get_event_list(struct perf_event *event)
|
||||
&event->pmu_ctx->flexible_active;
|
||||
}
|
||||
|
||||
/* @sibling must already be unlinked from its old leader's sibling_list. */
|
||||
static void perf_promote_sibling_to_leader(struct perf_event *sibling,
|
||||
struct perf_event_context *ctx,
|
||||
int group_caps)
|
||||
{
|
||||
/*
|
||||
* Events that have PERF_EV_CAP_SIBLING require being part of
|
||||
* a group and cannot exist on their own, schedule them out
|
||||
* and move them into the ERROR state. Also see
|
||||
* _perf_event_enable(), it will not be able to recover this
|
||||
* ERROR state.
|
||||
*/
|
||||
if (sibling->event_caps & PERF_EV_CAP_SIBLING)
|
||||
__event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR);
|
||||
|
||||
sibling->group_leader = sibling;
|
||||
sibling->group_caps = group_caps;
|
||||
|
||||
if (sibling->attach_state & PERF_ATTACH_CONTEXT) {
|
||||
add_event_to_groups(sibling, ctx);
|
||||
|
||||
if (sibling->state == PERF_EVENT_STATE_ACTIVE)
|
||||
list_add_tail(&sibling->active_list, get_event_list(sibling));
|
||||
}
|
||||
|
||||
perf_event__header_size(sibling);
|
||||
}
|
||||
|
||||
static void perf_group_detach(struct perf_event *event)
|
||||
{
|
||||
struct perf_event *leader = event->group_leader;
|
||||
@@ -2366,8 +2394,9 @@ static void perf_group_detach(struct perf_event *event)
|
||||
*/
|
||||
if (leader != event) {
|
||||
list_del_init(&event->sibling_list);
|
||||
event->group_leader->nr_siblings--;
|
||||
event->group_leader->group_generation++;
|
||||
leader->nr_siblings--;
|
||||
leader->group_generation++;
|
||||
perf_promote_sibling_to_leader(event, ctx, event->event_caps);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -2377,32 +2406,14 @@ static void perf_group_detach(struct perf_event *event)
|
||||
* to whatever list we are on.
|
||||
*/
|
||||
list_for_each_entry_safe(sibling, tmp, &event->sibling_list, sibling_list) {
|
||||
|
||||
/*
|
||||
* Events that have PERF_EV_CAP_SIBLING require being part of
|
||||
* a group and cannot exist on their own, schedule them out
|
||||
* and move them into the ERROR state. Also see
|
||||
* _perf_event_enable(), it will not be able to recover this
|
||||
* ERROR state.
|
||||
*/
|
||||
if (sibling->event_caps & PERF_EV_CAP_SIBLING)
|
||||
__event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR);
|
||||
|
||||
sibling->group_leader = sibling;
|
||||
list_del_init(&sibling->sibling_list);
|
||||
|
||||
/* Inherit group flags from the previous leader */
|
||||
sibling->group_caps = event->group_caps;
|
||||
|
||||
if (sibling->attach_state & PERF_ATTACH_CONTEXT) {
|
||||
add_event_to_groups(sibling, event->ctx);
|
||||
|
||||
if (sibling->state == PERF_EVENT_STATE_ACTIVE)
|
||||
list_add_tail(&sibling->active_list, get_event_list(sibling));
|
||||
}
|
||||
perf_promote_sibling_to_leader(sibling, ctx, event->group_caps);
|
||||
|
||||
WARN_ON_ONCE(sibling->ctx != event->ctx);
|
||||
}
|
||||
event->nr_siblings = 0;
|
||||
|
||||
out:
|
||||
for_each_sibling_event(tmp, leader)
|
||||
@@ -2592,12 +2603,7 @@ __perf_remove_from_context(struct perf_event *event,
|
||||
if (flags & DETACH_DEAD)
|
||||
state = PERF_EVENT_STATE_DEAD;
|
||||
|
||||
event_sched_out(event, ctx);
|
||||
|
||||
if (event->state > PERF_EVENT_STATE_OFF)
|
||||
perf_cgroup_event_disable(event, ctx);
|
||||
|
||||
perf_event_set_state(event, min(event->state, state));
|
||||
__event_disable(event, ctx, state);
|
||||
|
||||
if (flags & DETACH_GROUP)
|
||||
perf_group_detach(event);
|
||||
@@ -2666,8 +2672,9 @@ static void __event_disable(struct perf_event *event,
|
||||
enum perf_event_state state)
|
||||
{
|
||||
event_sched_out(event, ctx);
|
||||
perf_cgroup_event_disable(event, ctx);
|
||||
perf_event_set_state(event, state);
|
||||
if (event->state > PERF_EVENT_STATE_OFF)
|
||||
perf_cgroup_event_disable(event, ctx);
|
||||
perf_event_set_state(event, min(event->state, state));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -13972,7 +13979,7 @@ SYSCALL_DEFINE5(perf_event_open,
|
||||
goto err_fd;
|
||||
}
|
||||
group_leader = fd_file(group)->private_data;
|
||||
if (group_leader->state <= PERF_EVENT_STATE_REVOKED) {
|
||||
if (group_leader->state <= PERF_EVENT_STATE_EXIT) {
|
||||
err = -ENODEV;
|
||||
goto err_fd;
|
||||
}
|
||||
@@ -14103,6 +14110,12 @@ SYSCALL_DEFINE5(perf_event_open,
|
||||
if (group_leader->ctx != ctx)
|
||||
goto err_locked;
|
||||
|
||||
/* Recheck under ctx::mutex to serialize against remove-on-exec. */
|
||||
if (group_leader->state <= PERF_EVENT_STATE_EXIT) {
|
||||
err = -ENODEV;
|
||||
goto err_locked;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only a group leader can be exclusive or pinned
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user