Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

libbpf: Fix possible use-after-free for externs

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: 9d0a23313b1a ("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

authored by

Adin Scannell and committed by
Andrii Nakryiko
fa6f092c 19011398

+23 -3
+7 -3
tools/lib/bpf/libbpf.c
··· 597 597 int sym_idx; 598 598 int btf_id; 599 599 int sec_btf_id; 600 - const char *name; 600 + char *name; 601 601 char *essent_name; 602 602 bool is_set; 603 603 bool is_weak; ··· 4259 4259 return ext->btf_id; 4260 4260 } 4261 4261 t = btf__type_by_id(obj->btf, ext->btf_id); 4262 - ext->name = btf__name_by_offset(obj->btf, t->name_off); 4262 + ext->name = strdup(btf__name_by_offset(obj->btf, t->name_off)); 4263 + if (!ext->name) 4264 + return -ENOMEM; 4263 4265 ext->sym_idx = i; 4264 4266 ext->is_weak = ELF64_ST_BIND(sym->st_info) == STB_WEAK; 4265 4267 ··· 9140 9138 zfree(&obj->btf_custom_path); 9141 9139 zfree(&obj->kconfig); 9142 9140 9143 - for (i = 0; i < obj->nr_extern; i++) 9141 + for (i = 0; i < obj->nr_extern; i++) { 9142 + zfree(&obj->externs[i].name); 9144 9143 zfree(&obj->externs[i].essent_name); 9144 + } 9145 9145 9146 9146 zfree(&obj->externs); 9147 9147 obj->nr_extern = 0;
+16
tools/testing/selftests/bpf/progs/test_global_map_resize.c
··· 32 32 33 33 int percpu_arr[1] SEC(".data.percpu_arr"); 34 34 35 + /* at least one extern is included, to ensure that a specific 36 + * regression is tested whereby resizing resulted in a free-after-use 37 + * bug after type information is invalidated by the resize operation. 38 + * 39 + * There isn't a particularly good API to test for this specific condition, 40 + * but by having externs for the resizing tests it will cover this path. 41 + */ 42 + extern int LINUX_KERNEL_VERSION __kconfig; 43 + long version_sink; 44 + 35 45 SEC("tp/syscalls/sys_enter_getpid") 36 46 int bss_array_sum(void *ctx) 37 47 { ··· 53 43 54 44 for (size_t i = 0; i < bss_array_len; ++i) 55 45 sum += array[i]; 46 + 47 + /* see above; ensure this is not optimized out */ 48 + version_sink = LINUX_KERNEL_VERSION; 56 49 57 50 return 0; 58 51 } ··· 71 58 72 59 for (size_t i = 0; i < data_array_len; ++i) 73 60 sum += my_array[i]; 61 + 62 + /* see above; ensure this is not optimized out */ 63 + version_sink = LINUX_KERNEL_VERSION; 74 64 75 65 return 0; 76 66 }