Skip to contentSkip to conversation

Fix use-after-free race in bpf_prog_info synthesis

PATCH In review Ian Rogers 2 participants 6 messages Sep 2, 2025

Subsystems (2)
lore
Share & tools
lore

Ian Rogers

author reviewer
#0 lore raw

cover Fix use-after-free race in bpf_prog_info synthesis

The addition of more use of bpf_prog_info for gather BPF metadata in:
https://lore.kernel.org/all/[email protected]/
and the ever richer perf trace testing, such as:
https://lore.kernel.org/all/[email protected]/
frequently triggered a latent perf bug in v6.17 when the perf and libbpf updates came together. The bug would cause segvs and was reported here:
https://lore.kernel.org/lkml/CAP-5=fWJQcmUOP7MuCA2ihKnDAHUCOBLkQFEkQES-1ZZTrgf8Q@mail.gmail.com/

To fix the issue the 1st and 3rd patch are necessary. Both patches address a race of either the sideband thread updating perf's state or the kernel state changing over two system calls.

The use-after-free was introduced by:
https://lore.kernel.org/r/[email protected]
The lack of failing getting the bpf_prog_info for changes in the kernel was introduced in:
https://lore.kernel.org/r/[email protected]

As v6.17 is currently actively segv-ing in perf test I'd recommend these patches go into v6.17 asap.

When running the perf tests on v6.17 I frequently see less critical test failures addressed in:
https://lore.kernel.org/all/[email protected]/

Ian Rogers (3):
perf bpf-event: Fix use-after-free in synthesis
perf bpf-utils: Constify bpil_array_desc
perf bpf-utils: Harden get_bpf_prog_info_linear

 tools/perf/util/bpf-event.c | 39 ++++++++++++++++--------
 tools/perf/util/bpf-utils.c | 61 ++++++++++++++++++++++++-------------
 2 files changed, 66 insertions(+), 34 deletions(-)
Signature · 1 line
2.51.0.355.g5224444f11-goog
author reviewer
#1 lore raw

PATCH 1/3 perf bpf-event: Fix use-after-free in synthesis

Calls to perf_env__insert_bpf_prog_info may fail as a sideband thread may already have inserted the bpf_prog_info. Such failures may yield info_linear being freed which then causes use-after-free issues with the internal bpf_prog_info info struct. Make it so that perf_env__insert_bpf_prog_info trigger early non-error paths and fix the use-after-free in perf_event__synthesize_one_bpf_prog. Add proper return error handling to perf_env__add_bpf_info (that calls perf_env__insert_bpf_prog_info) and propagate the return value in its callers.

Fixes 03edb7020bb9 ("perf bpf: Fix two memory leakages when calling perf_env__insert_bpf_prog_info()")
---
 tools/perf/util/bpf-event.c | 39 +++++++++++++++++++++++++------------
 1 file changed, 27 insertions(+), 12 deletions(-)
tools/perf/util/bpf-event.c +27 -12
index 5b6d3e899e11..2298cd396c42 100644
@@ -657,9 +657,15 @@ static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
 		info_node->info_linear = info_linear;
 		info_node->metadata = NULL;
 		if (!perf_env__insert_bpf_prog_info(env, info_node)) {
-			free(info_linear);
+			/*
+			 * Insert failed, likely because of a duplicate event
+			 * made by the sideband thread. Ignore synthesizing the
+			 * metadata.
+			 */
 			free(info_node);
+			goto out;
 		}
+		/* info_linear is now owned by info_node and shouldn't be freed below. */
 		info_linear = NULL;
 
 		/*
@@ -827,18 +833,18 @@ int perf_event__synthesize_bpf_events(struct perf_session *session,
 	return err;
 }
 
-static void perf_env__add_bpf_info(struct perf_env *env, u32 id)
+static int perf_env__add_bpf_info(struct perf_env *env, u32 id)
 {
 	struct bpf_prog_info_node *info_node;
 	struct perf_bpil *info_linear;
 	struct btf *btf = NULL;
 	u64 arrays;
 	u32 btf_id;
-	int fd;
+	int fd, err = 0;
 
 	fd = bpf_prog_get_fd_by_id(id);
 	if (fd < 0)
-		return;
+		return -EINVAL;
 
 	arrays = 1UL << PERF_BPIL_JITED_KSYMS;
 	arrays |= 1UL << PERF_BPIL_JITED_FUNC_LENS;
@@ -852,6 +858,7 @@ static void perf_env__add_bpf_info(struct perf_env *env, u32 id)
 	info_linear = get_bpf_prog_info_linear(fd, arrays);
 	if (IS_ERR_OR_NULL(info_linear)) {
 		pr_debug("%s: failed to get BPF program info. aborting\n", __func__);
+		err = PTR_ERR(info_linear);
 		goto out;
 	}
 
@@ -862,38 +869,46 @@ static void perf_env__add_bpf_info(struct perf_env *env, u32 id)
 		info_node->info_linear = info_linear;
 		info_node->metadata = bpf_metadata_create(&info_linear->info);
 		if (!perf_env__insert_bpf_prog_info(env, info_node)) {
+			pr_debug("%s: duplicate add bpf info request for id %u\n",
+				 __func__, btf_id);
 			free(info_linear);
 			free(info_node);
+			goto out;
 		}
-	} else
+	} else {
 		free(info_linear);
+		err = -ENOMEM;
+		goto out;
+	}
 
 	if (btf_id == 0)
 		goto out;
 
 	btf = btf__load_from_kernel_by_id(btf_id);
-	if (libbpf_get_error(btf)) {
-		pr_debug("%s: failed to get BTF of id %u, aborting\n",
-			 __func__, btf_id);
-		goto out;
+	if (!btf) {
+		err = -errno;
+		pr_debug("%s: failed to get BTF of id %u %d\n", __func__, btf_id, err);
+	} else {
+		perf_env__fetch_btf(env, btf_id, btf);
 	}
-	perf_env__fetch_btf(env, btf_id, btf);
 
 out:
 	btf__free(btf);
 	close(fd);
+	return err;
 }
 
 static int bpf_event__sb_cb(union perf_event *event, void *data)
 {
 	struct perf_env *env = data;
+	int ret = 0;
 
 	if (event->header.type != PERF_RECORD_BPF_EVENT)
 		return -1;
 
 	switch (event->bpf.type) {
 	case PERF_BPF_EVENT_PROG_LOAD:
-		perf_env__add_bpf_info(env, event->bpf.id);
+		ret = perf_env__add_bpf_info(env, event->bpf.id);
 
 	case PERF_BPF_EVENT_PROG_UNLOAD:
 		/*
@@ -907,7 +922,7 @@ static int bpf_event__sb_cb(union perf_event *event, void *data)
 		break;
 	}
 
-	return 0;
+	return ret;
 }
 
 int evlist__add_bpf_sb_event(struct evlist *evlist, struct perf_env *env)
Signature · 1 line
2.51.0.355.g5224444f11-goog
author reviewer
#2 lore raw

PATCH 2/3 perf bpf-utils: Constify bpil_array_desc

In reply to Ian Rogers

The array's contents is a compile time constant. Constify to make the code more intention revealing and avoid unintended errors.

---
 tools/perf/util/bpf-utils.c | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)
tools/perf/util/bpf-utils.c +6 -12
index 80b1d2b3729b..64a558344696 100644
@@ -20,7 +20,7 @@ struct bpil_array_desc {
 				 */
 };
 
-static struct bpil_array_desc bpil_array_desc[] = {
+static const struct bpil_array_desc bpil_array_desc[] = {
 	[PERF_BPIL_JITED_INSNS] = {
 		offsetof(struct bpf_prog_info, jited_prog_insns),
 		offsetof(struct bpf_prog_info, jited_prog_len),
@@ -129,12 +129,10 @@ get_bpf_prog_info_linear(int fd, __u64 arrays)
 
 	/* step 2: calculate total size of all arrays */
 	for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
+		const struct bpil_array_desc *desc = &bpil_array_desc[i];
 		bool include_array = (arrays & (1UL << i)) > 0;
-		struct bpil_array_desc *desc;
 		__u32 count, size;
 
-		desc = bpil_array_desc + i;
-
 		/* kernel is too old to support this field */
 		if (info_len < desc->array_offset + sizeof(__u32) ||
 		    info_len < desc->count_offset + sizeof(__u32) ||
@@ -163,13 +161,12 @@ get_bpf_prog_info_linear(int fd, __u64 arrays)
 	ptr = info_linear->data;
 
 	for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
-		struct bpil_array_desc *desc;
+		const struct bpil_array_desc *desc = &bpil_array_desc[i];
 		__u32 count, size;
 
 		if ((arrays & (1UL << i)) == 0)
 			continue;
 
-		desc  = bpil_array_desc + i;
 		count = bpf_prog_info_read_offset_u32(&info, desc->count_offset);
 		size  = bpf_prog_info_read_offset_u32(&info, desc->size_offset);
 		bpf_prog_info_set_offset_u32(&info_linear->info,
@@ -192,13 +189,12 @@ get_bpf_prog_info_linear(int fd, __u64 arrays)
 
 	/* step 6: verify the data */
 	for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
-		struct bpil_array_desc *desc;
+		const struct bpil_array_desc *desc = &bpil_array_desc[i];
 		__u32 v1, v2;
 
 		if ((arrays & (1UL << i)) == 0)
 			continue;
 
-		desc = bpil_array_desc + i;
 		v1 = bpf_prog_info_read_offset_u32(&info, desc->count_offset);
 		v2 = bpf_prog_info_read_offset_u32(&info_linear->info,
 						   desc->count_offset);
@@ -224,13 +220,12 @@ void bpil_addr_to_offs(struct perf_bpil *info_linear)
 	int i;
 
 	for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
-		struct bpil_array_desc *desc;
+		const struct bpil_array_desc *desc = &bpil_array_desc[i];
 		__u64 addr, offs;
 
 		if ((info_linear->arrays & (1UL << i)) == 0)
 			continue;
 
-		desc = bpil_array_desc + i;
 		addr = bpf_prog_info_read_offset_u64(&info_linear->info,
 						     desc->array_offset);
 		offs = addr - ptr_to_u64(info_linear->data);
@@ -244,13 +239,12 @@ void bpil_offs_to_addr(struct perf_bpil *info_linear)
 	int i;
 
 	for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
-		struct bpil_array_desc *desc;
+		const struct bpil_array_desc *desc = &bpil_array_desc[i];
 		__u64 addr, offs;
 
 		if ((info_linear->arrays & (1UL << i)) == 0)
 			continue;
 
-		desc = bpil_array_desc + i;
 		offs = bpf_prog_info_read_offset_u64(&info_linear->info,
 						     desc->array_offset);
 		addr = offs + ptr_to_u64(info_linear->data);
Signature · 1 line
2.51.0.355.g5224444f11-goog
author reviewer
#3 lore raw

PATCH 3/3 perf bpf-utils: Harden get_bpf_prog_info_linear

In reply to Ian Rogers

In get_bpf_prog_info_linear two calls to bpf_obj_get_info_by_fd are made, the first to compute memory requirements for a struct perf_bpil and the second to fill it in. Previously the code would warn when the second call didn't match the first. Such races can be common place in things like perf test, whose perf trace tests will frequently load BPF programs. Rather than a debug message, return actual errors for this case. Out of paranoia also validate the read bpf_prog_info array value. Change the type of ptr to avoid mismatched pointer type compiler warnings. Add some additional debug print outs and sanity asserts.

Fixes 6ac22d036f86 ("perf bpf: Pull in bpf_program__get_prog_info_linear()")
---
 tools/perf/util/bpf-utils.c | 43 ++++++++++++++++++++++++++++---------
 1 file changed, 33 insertions(+), 10 deletions(-)
tools/perf/util/bpf-utils.c +33 -10
index 64a558344696..5a66dc8594aa 100644
@@ -115,7 +115,7 @@ get_bpf_prog_info_linear(int fd, __u64 arrays)
 	__u32 info_len = sizeof(info);
 	__u32 data_len = 0;
 	int i, err;
-	void *ptr;
+	__u8 *ptr;
 
 	if (arrays >> PERF_BPIL_LAST_ARRAY)
 		return ERR_PTR(-EINVAL);
@@ -126,6 +126,8 @@ get_bpf_prog_info_linear(int fd, __u64 arrays)
 		pr_debug("can't get prog info: %s", strerror(errno));
 		return ERR_PTR(-EFAULT);
 	}
+	if (info.type >= __MAX_BPF_PROG_TYPE)
+		pr_debug("%s:%d: unexpected program type %u\n", __func__, __LINE__, info.type);
 
 	/* step 2: calculate total size of all arrays */
 	for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
@@ -173,6 +175,8 @@ get_bpf_prog_info_linear(int fd, __u64 arrays)
 					     desc->count_offset, count);
 		bpf_prog_info_set_offset_u32(&info_linear->info,
 					     desc->size_offset, size);
+		assert(ptr >= info_linear->data);
+		assert(ptr < &info_linear->data[data_len]);
 		bpf_prog_info_set_offset_u64(&info_linear->info,
 					     desc->array_offset,
 					     ptr_to_u64(ptr));
@@ -186,26 +190,45 @@ get_bpf_prog_info_linear(int fd, __u64 arrays)
 		free(info_linear);
 		return ERR_PTR(-EFAULT);
 	}
+	if (info_linear->info.type >= __MAX_BPF_PROG_TYPE) {
+		pr_debug("%s:%d: unexpected program type %u\n",
+			 __func__, __LINE__, info_linear->info.type);
+	}
 
 	/* step 6: verify the data */
+	ptr = info_linear->data;
 	for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
 		const struct bpil_array_desc *desc = &bpil_array_desc[i];
-		__u32 v1, v2;
+		__u32 count1, count2, size1, size2;
+		__u64 ptr2;
 
 		if ((arrays & (1UL << i)) == 0)
 			continue;
 
-		v1 = bpf_prog_info_read_offset_u32(&info, desc->count_offset);
-		v2 = bpf_prog_info_read_offset_u32(&info_linear->info,
+		count1 = bpf_prog_info_read_offset_u32(&info, desc->count_offset);
+		count2 = bpf_prog_info_read_offset_u32(&info_linear->info,
 						   desc->count_offset);
-		if (v1 != v2)
-			pr_warning("%s: mismatch in element count\n", __func__);
+		if (count1 != count2) {
+			pr_warning("%s: mismatch in element count %u vs %u\n", __func__, count1, count2);
+			free(info_linear);
+			return ERR_PTR(-ERANGE);
+		}
 
-		v1 = bpf_prog_info_read_offset_u32(&info, desc->size_offset);
-		v2 = bpf_prog_info_read_offset_u32(&info_linear->info,
+		size1 = bpf_prog_info_read_offset_u32(&info, desc->size_offset);
+		size2 = bpf_prog_info_read_offset_u32(&info_linear->info,
 						   desc->size_offset);
-		if (v1 != v2)
-			pr_warning("%s: mismatch in rec size\n", __func__);
+		if (size1 != size2) {
+			pr_warning("%s: mismatch in rec size %u vs %u\n", __func__, size1, size2);
+			free(info_linear);
+			return ERR_PTR(-ERANGE);
+		}
+		ptr2 = bpf_prog_info_read_offset_u64(&info_linear->info, desc->array_offset);
+		if (ptr_to_u64(ptr) != ptr2) {
+			pr_warning("%s: mismatch in array %p vs %llx\n", __func__, ptr, ptr2);
+			free(info_linear);
+			return ERR_PTR(-ERANGE);
+		}
+		ptr += roundup(count1 * size1, sizeof(__u64));
 	}
 
 	/* step 7: update info_len and data_len */
Signature · 1 line
2.51.0.355.g5224444f11-goog
maintainer Reviewed-by on the v1 series
#4 lore raw
In reply to Ian Rogers

Hi Ian,

Quoted 11 lines · Ian Rogers

The addition of more use of bpf_prog_info for gather BPF metadata in:
https://lore.kernel.org/all/[email protected]/
and the ever richer perf trace testing, such as:
https://lore.kernel.org/all/[email protected]/
frequently triggered a latent perf bug in v6.17 when the perf and libbpf updates came together. The bug would cause segvs and was reported here:
https://lore.kernel.org/lkml/CAP-5=fWJQcmUOP7MuCA2ihKnDAHUCOBLkQFEkQES-1ZZTrgf8Q@mail.gmail.com/

To fix the issue the 1st and 3rd patch are necessary. Both patches address a race of either the sideband thread updating perf's state or the kernel state changing over two system calls.

Thanks a lot for the fix!

Quoted 9 lines

The use-after-free was introduced by:
https://lore.kernel.org/r/[email protected]
The lack of failing getting the bpf_prog_info for changes in the kernel was introduced in:
https://lore.kernel.org/r/[email protected]

As v6.17 is currently actively segv-ing in perf test I'd recommend these patches go into v6.17 asap.

Sure, I'll add them to perf-tools tree.

Quoted 4 lines

When running the perf tests on v6.17 I frequently see less critical test failures addressed in:
https://lore.kernel.org/all/[email protected]/

Are they all from v6.17?

Quoted 5 lines

Ian Rogers (3):
perf bpf-event: Fix use-after-free in synthesis
perf bpf-utils: Constify bpil_array_desc
perf bpf-utils: Harden get_bpf_prog_info_linear

Thanks,
Namhyung

Quoted 8 lines
 tools/perf/util/bpf-event.c | 39 ++++++++++++++++--------
 tools/perf/util/bpf-utils.c | 61 ++++++++++++++++++++++++-------------
 2 files changed, 66 insertions(+), 34 deletions(-)
Signature · 1 line
2.51.0.355.g5224444f11-goog
author reviewer
#5 lore raw
Quoted 12 lines · Namhyung Kim

Hi Ian,

Quoted 8 lines · Ian Rogers

The addition of more use of bpf_prog_info for gather BPF metadata in:
https://lore.kernel.org/all/[email protected]/
and the ever richer perf trace testing, such as:
https://lore.kernel.org/all/[email protected]/
frequently triggered a latent perf bug in v6.17 when the perf and libbpf updates came together. The bug would cause segvs and was reported here:
https://lore.kernel.org/lkml/CAP-5=fWJQcmUOP7MuCA2ihKnDAHUCOBLkQFEkQES-1ZZTrgf8Q@mail.gmail.com/

Quoted 5 lines
Quoted 3 lines

To fix the issue the 1st and 3rd patch are necessary. Both patches address a race of either the sideband thread updating perf's state or the kernel state changing over two system calls.

Thanks a lot for the fix!

Thanks, your reproduction was a great help.

Quoted 10 lines
Quoted 9 lines

The use-after-free was introduced by:
https://lore.kernel.org/r/[email protected]
The lack of failing getting the bpf_prog_info for changes in the kernel was introduced in:
https://lore.kernel.org/r/[email protected]

As v6.17 is currently actively segv-ing in perf test I'd recommend these patches go into v6.17 asap.

Quoted 8 lines

Sure, I'll add them to perf-tools tree.

Quoted 4 lines

When running the perf tests on v6.17 I frequently see less critical test failures addressed in:
https://lore.kernel.org/all/[email protected]/

Are they all from v6.17?

We could wait for the backports to v6.17.1, they all have fixes tags. They are more cosmetic things than this set of fixes.

Thanks,
Ian

Quoted 20 lines
Quoted 6 lines

Ian Rogers (3):
perf bpf-event: Fix use-after-free in synthesis
perf bpf-utils: Constify bpil_array_desc
perf bpf-utils: Harden get_bpf_prog_info_linear

Thanks,
Namhyung

Quoted 8 lines
 tools/perf/util/bpf-event.c | 39 ++++++++++++++++--------
 tools/perf/util/bpf-utils.c | 61 ++++++++++++++++++++++++-------------
 2 files changed, 66 insertions(+), 34 deletions(-)

--
2.51.0.355.g5224444f11-goog