libbpf: Fix possible use-after-free for externs

[ Upstream commit fa6f092cc0a02d0fcee37e9e8172eda372a03d33 ]

The `name` field in `obj->externs` points into the BTF data at initial
open time. However, some functions may invalidate this after opening and
before loading (e.g. `bpf_map__set_value_size`), which results in
pointers into freed memory and undefined behavior.

The simplest solution is to simply `strdup` these strings, similar to
the `essent_name`, and free them at the same time.

In order to test this path, the `global_map_resize` BPF selftest is
modified slightly to ensure the presence of an extern, which causes this
test to fail prior to the fix. Given there isn't an obvious API or error
to test against, I opted to add this to the existing test as an aspect
of the resizing feature rather than duplicate the test.

Fixes: 9d0a23313b ("libbpf: Add capability for resizing datasec maps")
Signed-off-by: Adin Scannell <amscanne@meta.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20250625050215.2777374-1-amscanne@meta.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
(cherry picked from commit ad9c4941d32e1c35d878b50a2e7036282b8a213a)
This commit is contained in:
Adin Scannell 2025-06-24 22:02:15 -07:00 committed by Wentao Guan
parent b19e28953b
commit 1fe72f1273
2 changed files with 23 additions and 3 deletions

View File

@ -554,7 +554,7 @@ struct extern_desc {
int sym_idx;
int btf_id;
int sec_btf_id;
const char *name;
char *name;
char *essent_name;
bool is_set;
bool is_weak;
@ -3822,7 +3822,9 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
return ext->btf_id;
}
t = btf__type_by_id(obj->btf, ext->btf_id);
ext->name = btf__name_by_offset(obj->btf, t->name_off);
ext->name = strdup(btf__name_by_offset(obj->btf, t->name_off));
if (!ext->name)
return -ENOMEM;
ext->sym_idx = i;
ext->is_weak = ELF64_ST_BIND(sym->st_info) == STB_WEAK;
@ -8457,8 +8459,10 @@ void bpf_object__close(struct bpf_object *obj)
zfree(&obj->btf_custom_path);
zfree(&obj->kconfig);
for (i = 0; i < obj->nr_extern; i++)
for (i = 0; i < obj->nr_extern; i++) {
zfree(&obj->externs[i].name);
zfree(&obj->externs[i].essent_name);
}
zfree(&obj->externs);
obj->nr_extern = 0;

View File

@ -31,6 +31,16 @@ int my_int_last SEC(".data.array_not_last");
int percpu_arr[1] SEC(".data.percpu_arr");
/* at least one extern is included, to ensure that a specific
* regression is tested whereby resizing resulted in a free-after-use
* bug after type information is invalidated by the resize operation.
*
* There isn't a particularly good API to test for this specific condition,
* but by having externs for the resizing tests it will cover this path.
*/
extern int LINUX_KERNEL_VERSION __kconfig;
long version_sink;
SEC("tp/syscalls/sys_enter_getpid")
int bss_array_sum(void *ctx)
{
@ -43,6 +53,9 @@ int bss_array_sum(void *ctx)
for (size_t i = 0; i < bss_array_len; ++i)
sum += array[i];
/* see above; ensure this is not optimized out */
version_sink = LINUX_KERNEL_VERSION;
return 0;
}
@ -58,5 +71,8 @@ int data_array_sum(void *ctx)
for (size_t i = 0; i < data_array_len; ++i)
sum += my_array[i];
/* see above; ensure this is not optimized out */
version_sink = LINUX_KERNEL_VERSION;
return 0;
}