Skip to content

Commit 68b17ca

Browse files
perf: Disallow mis-matched inherited group reads
jira VULN-6760 cve CVE-2023-5717 commit-author Peter Zijlstra <[email protected]> commit 32671e3 upstream-diff This patch causes kABI breakage due to a change in the struct perf_event layout after adding the group_generation field. Hence, to preserve kABI compatibility, the RH_KABI_EXTEND macro is used to safely append the new field without affecting the existing layout. Because group consistency is non-atomic between parent (filedesc) and children (inherited) events, it is possible for PERF_FORMAT_GROUP read() to try and sum non-matching counter groups -- with non-sensical results. Add group_generation to distinguish the case where a parent group removes and adds an event and thus has the same number, but a different configuration of events as inherited groups. This became a problem when commit fa8c269 ("perf/core: Invert perf_read_group() loops") flipped the order of child_list and sibling_list. Previously it would iterate the group (sibling_list) first, and for each sibling traverse the child_list. In this order, only the group composition of the parent is relevant. By flipping the order the group composition of the child (inherited) events becomes an issue and the mis-match in group composition becomes evident. That said; even prior to this commit, while reading of a group that is not equally inherited was not broken, it still made no sense. (Ab)use ECHILD as error return to indicate issues with child process group composition. Fixes: fa8c269 ("perf/core: Invert perf_read_group() loops") Reported-by: Budimir Markovic <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Link: https://lkml.kernel.org/r/[email protected] (cherry picked from commit 32671e3) Signed-off-by: Shreeya Patel <[email protected]>
1 parent e656ea9 commit 68b17ca

File tree

2 files changed

+36
-6
lines changed

2 files changed

+36
-6
lines changed

include/linux/perf_event.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct perf_guest_info_callbacks {
6161
#include <linux/security.h>
6262
#include <linux/static_call.h>
6363
#include <linux/lockdep.h>
64+
#include <linux/rh_kabi.h>
6465
#include <asm/local.h>
6566

6667
struct perf_callchain_entry {
@@ -803,6 +804,8 @@ struct perf_event {
803804
void *security;
804805
#endif
805806
struct list_head sb_list;
807+
808+
RH_KABI_EXTEND(unsigned int group_generation)
806809
#endif /* CONFIG_PERF_EVENTS */
807810
};
808811

kernel/events/core.c

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,7 @@ static void perf_group_attach(struct perf_event *event)
19561956

19571957
list_add_tail(&event->sibling_list, &group_leader->sibling_list);
19581958
group_leader->nr_siblings++;
1959+
group_leader->group_generation++;
19591960

19601961
perf_event__header_size(group_leader);
19611962

@@ -2150,6 +2151,7 @@ static void perf_group_detach(struct perf_event *event)
21502151
if (leader != event) {
21512152
list_del_init(&event->sibling_list);
21522153
event->group_leader->nr_siblings--;
2154+
event->group_leader->group_generation++;
21532155
goto out;
21542156
}
21552157

@@ -5239,7 +5241,7 @@ static int __perf_read_group_add(struct perf_event *leader,
52395241
u64 read_format, u64 *values)
52405242
{
52415243
struct perf_event_context *ctx = leader->ctx;
5242-
struct perf_event *sub;
5244+
struct perf_event *sub, *parent;
52435245
unsigned long flags;
52445246
int n = 1; /* skip @nr */
52455247
int ret;
@@ -5249,6 +5251,33 @@ static int __perf_read_group_add(struct perf_event *leader,
52495251
return ret;
52505252

52515253
raw_spin_lock_irqsave(&ctx->lock, flags);
5254+
/*
5255+
* Verify the grouping between the parent and child (inherited)
5256+
* events is still in tact.
5257+
*
5258+
* Specifically:
5259+
* - leader->ctx->lock pins leader->sibling_list
5260+
* - parent->child_mutex pins parent->child_list
5261+
* - parent->ctx->mutex pins parent->sibling_list
5262+
*
5263+
* Because parent->ctx != leader->ctx (and child_list nests inside
5264+
* ctx->mutex), group destruction is not atomic between children, also
5265+
* see perf_event_release_kernel(). Additionally, parent can grow the
5266+
* group.
5267+
*
5268+
* Therefore it is possible to have parent and child groups in a
5269+
* different configuration and summing over such a beast makes no sense
5270+
* what so ever.
5271+
*
5272+
* Reject this.
5273+
*/
5274+
parent = leader->parent;
5275+
if (parent &&
5276+
(parent->group_generation != leader->group_generation ||
5277+
parent->nr_siblings != leader->nr_siblings)) {
5278+
ret = -ECHILD;
5279+
goto unlock;
5280+
}
52525281

52535282
/*
52545283
* Since we co-schedule groups, {enabled,running} times of siblings
@@ -5282,8 +5311,9 @@ static int __perf_read_group_add(struct perf_event *leader,
52825311
values[n++] = atomic64_read(&sub->lost_samples);
52835312
}
52845313

5314+
unlock:
52855315
raw_spin_unlock_irqrestore(&ctx->lock, flags);
5286-
return 0;
5316+
return ret;
52875317
}
52885318

52895319
static int perf_read_group(struct perf_event *event,
@@ -5302,10 +5332,6 @@ static int perf_read_group(struct perf_event *event,
53025332

53035333
values[0] = 1 + leader->nr_siblings;
53045334

5305-
/*
5306-
* By locking the child_mutex of the leader we effectively
5307-
* lock the child list of all siblings.. XXX explain how.
5308-
*/
53095335
mutex_lock(&leader->child_mutex);
53105336

53115337
ret = __perf_read_group_add(leader, read_format, values);
@@ -13117,6 +13143,7 @@ static int inherit_group(struct perf_event *parent_event,
1311713143
!perf_get_aux_event(child_ctr, leader))
1311813144
return -EINVAL;
1311913145
}
13146+
leader->group_generation = parent_event->group_generation;
1312013147
return 0;
1312113148
}
1312213149

0 commit comments

Comments
 (0)