Pull perf events fix from Ingo Molnar: "Fix group event semantics" * tag 'perf-urgent-2023-10-21' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: perf: Disallow mis-matched inherited group reads
Showing the changes to kernel (1 of 2 files). Show all
Unified · Split
| @@ -1954,6 +1954,7 @@ static void perf_group_attach(struct perf_event *event) · 1953 unmodified lines | ||
| 1954 | 1954 | |
| 1955 | 1955 | list_add_tail(&event->sibling_list, &group_leader->sibling_list); |
| 1956 | 1956 | group_leader->nr_siblings++; |
| 1957 | + group_leader->group_generation++; | |
| 1957 | 1958 | |
| 1958 | 1959 | perf_event__header_size(group_leader); |
| 1959 | 1960 | |
| @@ -2144,6 +2145,7 @@ static void perf_group_detach(struct perf_event *event) · 184 unmodified lines | ||
| 2144 | 2145 | if (leader != event) { |
| 2145 | 2146 | list_del_init(&event->sibling_list); |
| 2146 | 2147 | event->group_leader->nr_siblings--; |
| 2148 | + event->group_leader->group_generation++; | |
| 2147 | 2149 | goto out; |
| 2148 | 2150 | } |
| 2149 | 2151 | |
| @@ -5440,7 +5442,7 @@ static int __perf_read_group_add(struct perf_event *leader, · 3290 unmodified lines | ||
| 5440 | 5442 | u64 read_format, u64 *values) |
| 5441 | 5443 | { |
| 5442 | 5444 | struct perf_event_context *ctx = leader->ctx; |
| 5443 | − struct perf_event *sub; | |
| 5445 | + struct perf_event *sub, *parent; | |
| 5444 | 5446 | unsigned long flags; |
| 5445 | 5447 | int n = 1; /* skip @nr */ |
| 5446 | 5448 | int ret; |
| @@ -5450,6 +5452,33 @@ static int __perf_read_group_add(struct perf_event *leader, · 3 unmodified lines | ||
| 5450 | 5452 | return ret; |
| 5451 | 5453 | |
| 5452 | 5454 | raw_spin_lock_irqsave(&ctx->lock, flags); |
| 5455 | + /* | |
| 5456 | + * Verify the grouping between the parent and child (inherited) | |
| 5457 | + * events is still in tact. | |
| 5458 | + * | |
| 5459 | + * Specifically: | |
| 5460 | + * - leader->ctx->lock pins leader->sibling_list | |
| 5461 | + * - parent->child_mutex pins parent->child_list | |
| 5462 | + * - parent->ctx->mutex pins parent->sibling_list | |
| 5463 | + * | |
| 5464 | + * Because parent->ctx != leader->ctx (and child_list nests inside | |
| 5465 | + * ctx->mutex), group destruction is not atomic between children, also | |
| 5466 | + * see perf_event_release_kernel(). Additionally, parent can grow the | |
| 5467 | + * group. | |
| 5468 | + * | |
| 5469 | + * Therefore it is possible to have parent and child groups in a | |
| 5470 | + * different configuration and summing over such a beast makes no sense | |
| 5471 | + * what so ever. | |
| 5472 | + * | |
| 5473 | + * Reject this. | |
| 5474 | + */ | |
| 5475 | + parent = leader->parent; | |
| 5476 | + if (parent && | |
| 5477 | + (parent->group_generation != leader->group_generation || | |
| 5478 | + parent->nr_siblings != leader->nr_siblings)) { | |
| 5479 | + ret = -ECHILD; | |
| 5480 | + goto unlock; | |
| 5481 | + } | |
| 5453 | 5482 | |
| 5454 | 5483 | /* |
| 5455 | 5484 | * Since we co-schedule groups, {enabled,running} times of siblings |
| @@ -5483,8 +5512,9 @@ static int __perf_read_group_add(struct perf_event *leader, · 27 unmodified lines | ||
| 5483 | 5512 | values[n++] = atomic64_read(&sub->lost_samples); |
| 5484 | 5513 | } |
| 5485 | 5514 | |
| 5515 | +unlock: | |
| 5486 | 5516 | raw_spin_unlock_irqrestore(&ctx->lock, flags); |
| 5487 | − return 0; | |
| 5517 | + return ret; | |
| 5488 | 5518 | } |
| 5489 | 5519 | |
| 5490 | 5520 | static int perf_read_group(struct perf_event *event, |
| @@ -5503,10 +5533,6 @@ static int perf_read_group(struct perf_event *event, · 12 unmodified lines | ||
| 5503 | 5533 | |
| 5504 | 5534 | values[0] = 1 + leader->nr_siblings; |
| 5505 | 5535 | |
| 5506 | − /* | |
| 5507 | − * By locking the child_mutex of the leader we effectively | |
| 5508 | − * lock the child list of all siblings.. XXX explain how. | |
| 5509 | − */ | |
| 5510 | 5536 | mutex_lock(&leader->child_mutex); |
| 5511 | 5537 | |
| 5512 | 5538 | ret = __perf_read_group_add(leader, read_format, values); |
| @@ -13346,6 +13372,7 @@ static int inherit_group(struct perf_event *parent_event, · 7833 unmodified lines | ||
| 13346 | 13372 | !perf_get_aux_event(child_ctr, leader)) |
| 13347 | 13373 | return -EINVAL; |
| 13348 | 13374 | } |
| 13375 | + leader->group_generation = parent_event->group_generation; | |
| 13349 | 13376 | return 0; |
| 13350 | 13377 | } |
| 13351 | 13378 | |