* [PATCH bpf 0/2] libbpf: Reject out-of-range linker relocation offsets @ 2026-06-14 5:39 HyeongJun An 2026-06-14 5:39 ` [PATCH bpf 1/2] " HyeongJun An 2026-06-14 5:39 ` [PATCH bpf 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset HyeongJun An 0 siblings, 2 replies; 5+ messages in thread From: HyeongJun An @ 2026-06-14 5:39 UTC (permalink / raw) To: Andrii Nakryiko, Alexei Starovoitov, Daniel Borkmann Cc: Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Shuah Khan, bpf, linux-kselftest, linux-kernel, HyeongJun An The libbpf static linker validates relocation type, symbol index and instruction alignment in linker_sanity_check_elf_relos(), but does not check that the relocation offset is inside the relocated section. A malformed BPF object processed by the static linker (e.g. via "bpftool gen object") can therefore carry an out-of-range r_offset that linker_append_elf_relos() then uses to index the section data, reading and writing past the buffer. The normal object-loading path already rejects such offsets (libbpf.c, rel->r_offset >= scn_data->d_size); the static linker path is the missing sibling. Patch 1 adds the same bound. Patch 2 adds a selftest that builds a tiny object with an out-of-range relocation offset and checks that the linker now rejects it, with a valid relocation as a positive control. Reproduced with ASAN: before patch 1 the out-of-range relocation is accepted (and triggers a heap-buffer-overflow); after, it is rejected with -EINVAL. HyeongJun An (2): libbpf: Reject out-of-range linker relocation offsets selftests/bpf: Test linker rejects out-of-range relocation offset tools/lib/bpf/linker.c | 6 + .../selftests/bpf/prog_tests/libbpf_linker.c | 212 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/libbpf_linker.c -- 2.43.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf 1/2] libbpf: Reject out-of-range linker relocation offsets 2026-06-14 5:39 [PATCH bpf 0/2] libbpf: Reject out-of-range linker relocation offsets HyeongJun An @ 2026-06-14 5:39 ` HyeongJun An 2026-06-14 7:50 ` sashiko-bot 2026-06-14 5:39 ` [PATCH bpf 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset HyeongJun An 1 sibling, 1 reply; 5+ messages in thread From: HyeongJun An @ 2026-06-14 5:39 UTC (permalink / raw) To: Andrii Nakryiko, Alexei Starovoitov, Daniel Borkmann Cc: Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Shuah Khan, bpf, linux-kselftest, linux-kernel, HyeongJun An, stable The static linker sanity-checks relocation sections before appending them, but for executable target sections it only verifies that r_offset is BPF-instruction aligned. It does not verify that the offset is inside the relocated section. A malformed object can therefore pass an out-of-range offset through linker_sanity_check_elf_relos(). When the relocation is against an STT_SECTION symbol, linker_append_elf_relos() uses the unchecked offset to find the instruction to adjust: insn = dst_linked_sec->raw_data + dst_rel->r_offset; and then reads insn->code and updates insn->imm. This is reproducible with bpftool's static linker by crafting a BPF object with a 16-byte executable section and a relocation in its .rel section whose r_offset is 0x1000: BUG: AddressSanitizer: heap-buffer-overflow in linker_append_elf_relos READ of size 1 linker_append_elf_relos bpf_linker_add_file bpf_linker__add_file do_object Reject relocation offsets that are outside the relocated section before any later use. This mirrors the normal object loading path, which already rejects executable relocations whose r_offset is not inside the program section. Fixes: faf6ed321cf6 ("libbpf: Add BPF static linker APIs") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: HyeongJun An <sammiee5311@gmail.com> --- tools/lib/bpf/linker.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/lib/bpf/linker.c b/tools/lib/bpf/linker.c index 78f92c39290a..3eb23da167d2 100644 --- a/tools/lib/bpf/linker.c +++ b/tools/lib/bpf/linker.c @@ -1048,6 +1048,12 @@ static int linker_sanity_check_elf_relos(struct src_obj *obj, struct src_sec *se return -EINVAL; } + if (relo->r_offset >= link_sec->shdr->sh_size) { + pr_warn("ELF relo #%d in section #%zu has invalid offset %zu in %s\n", + i, sec->sec_idx, (size_t)relo->r_offset, obj->filename); + return -EINVAL; + } + if (link_sec->shdr->sh_flags & SHF_EXECINSTR) { if (relo->r_offset % sizeof(struct bpf_insn) != 0) { pr_warn("ELF relo #%d in section #%zu points to missing symbol #%zu in %s\n", -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf 1/2] libbpf: Reject out-of-range linker relocation offsets 2026-06-14 5:39 ` [PATCH bpf 1/2] " HyeongJun An @ 2026-06-14 7:50 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-06-14 7:50 UTC (permalink / raw) To: HyeongJun An; +Cc: bpf Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: Pre-existing issues: - [Critical] The `sec_sz` field in `struct dst_sec` is declared as an `int` instead of `size_t`, which causes large section sizes to truncate into negative values and leads to severe heap buffer underflows. - [High] Static linker permits an out-of-bounds read when processing relocations against STT_SECTION symbols with a large st_shndx (e.g. SHN_ABS). - [High] Processing relocations for an executable SHT_NOBITS section causes a NULL pointer dereference. - [High] extend_sec() fails to properly handle realloc() returning NULL, leading to a NULL pointer dereference in memcpy(). -- commit 3c2b5b3ea26336642319803cdebc730e9790c0e6 Author: HyeongJun An <sammiee5311@gmail.com> libbpf: Reject out-of-range linker relocation offsets The static linker sanity-checks relocation sections before appending them, but for executable target sections it only verifies that r_offset is BPF-instruction aligned. It does not verify that the offset is inside the relocated section. > diff --git a/tools/lib/bpf/linker.c b/tools/lib/bpf/linker.c > index 78f92c39290af..3eb23da167d29 100644 > --- a/tools/lib/bpf/linker.c > +++ b/tools/lib/bpf/linker.c > @@ -1048,6 +1048,12 @@ static int linker_sanity_check_elf_relos(struct src_obj *obj, struct src_sec *se > return -EINVAL; > } > > + if (relo->r_offset >= link_sec->shdr->sh_size) { > + pr_warn("ELF relo #%d in section #%zu has invalid offset %zu in %s\n", > + i, sec->sec_idx, (size_t)relo->r_offset, obj->filename); > + return -EINVAL; > + } > + > if (link_sec->shdr->sh_flags & SHF_EXECINSTR) { > if (relo->r_offset % sizeof(struct bpf_insn) != 0) { > pr_warn("ELF relo #%d in section #%zu points to missing symbol #%zu in %s\n", [Severity: High] This is a pre-existing issue, but can this lead to an out-of-bounds read? tools/lib/bpf/linker.c:linker_append_elf_relos() { ... struct src_sec *sec = &obj->secs[src_sym->st_shndx]; ... } In linker_sanity_check_elf_symtab(), the bounds check against obj->sec_cnt is skipped if sym->st_shndx >= SHN_LORESERVE (such as SHN_ABS). However, STT_SECTION symbols might still use these reserved indices. Does this array access use the unvalidated 16-bit integer as an array index, potentially causing a heap out-of-bounds read? [Severity: High] This isn't a bug introduced by this patch, but could this cause a NULL pointer dereference? tools/lib/bpf/linker.c:linker_append_elf_relos() { ... insn = dst_linked_sec->raw_data + dst_rel->r_offset; if (insn->code == (BPF_JMP | BPF_CALL)) ... } For ELF sections of type SHT_NOBITS, extend_sec() does not allocate dst->raw_data, leaving it as NULL. Since an SHT_NOBITS section can still carry the SHF_EXECINSTR flag, does evaluating this pointer and reading insn->code cause a crash when applying relocations to an SHT_NOBITS executable section? [Severity: High] This is a pre-existing issue, but does this correctly handle memory allocation failures? tools/lib/bpf/linker.c:extend_sec() { ... if (!tmp && dst_align_sz > 0) return -ENOMEM; dst->raw_data = tmp; ... } For the very first object added, dst->sec_sz is 0, making dst_align_sz 0. If realloc() fails and returns NULL, the error check is bypassed. Since dst->raw_data is then set to NULL, does the subsequent memcpy() crash when attempting to copy data to address 0x0? [Severity: Critical] This is a pre-existing issue, but does using an int for sec_sz truncate large section sizes into negative values? tools/lib/bpf/linker.c:struct dst_sec { ... /* final output section size */ int sec_sz; ... }; tools/lib/bpf/linker.c:extend_sec() { ... dst_align_sz = (dst->sec_sz + dst_align - 1) / dst_align * dst_align; ... memcpy(dst->raw_data + dst_align_sz, src->data->d_buf, src->shdr->sh_size); ... } If a malformed ELF file specifies a very large section size, dst->sec_sz can truncate to a negative value. When a second object file is linked, the alignment calculation promotes the negative int to an unsigned 64-bit size_t, wrapping around to a massive size. Does this cause realloc() to allocate a small buffer while the subsequent memcpy() writes the new section data behind the newly allocated heap chunk, leading to a heap buffer underflow? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260614053927.160566-1-sammiee5311@gmail.com?part=1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset 2026-06-14 5:39 [PATCH bpf 0/2] libbpf: Reject out-of-range linker relocation offsets HyeongJun An 2026-06-14 5:39 ` [PATCH bpf 1/2] " HyeongJun An @ 2026-06-14 5:39 ` HyeongJun An 2026-06-14 5:53 ` sashiko-bot 1 sibling, 1 reply; 5+ messages in thread From: HyeongJun An @ 2026-06-14 5:39 UTC (permalink / raw) To: Andrii Nakryiko, Alexei Starovoitov, Daniel Borkmann Cc: Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, Shuah Khan, bpf, linux-kselftest, linux-kernel, HyeongJun An Add regression coverage for libbpf static linker relocation offset checks. Build a minimal ET_REL/EM_BPF object in memory with a 16-byte executable section and one relocation against that section. Check that a valid relocation offset is accepted and an offset outside the relocated section is rejected with -EINVAL. Assisted-by: Codex:gpt-5 Signed-off-by: HyeongJun An <sammiee5311@gmail.com> --- .../selftests/bpf/prog_tests/libbpf_linker.c | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/libbpf_linker.c diff --git a/tools/testing/selftests/bpf/prog_tests/libbpf_linker.c b/tools/testing/selftests/bpf/prog_tests/libbpf_linker.c new file mode 100644 index 000000000000..e7bec2434349 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/libbpf_linker.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <elf.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include <bpf/libbpf.h> +#include <linux/bpf.h> +#include <test_progs.h> + +enum { + SEC_NULL, + SEC_TEXT, + SEC_REL_TEXT, + SEC_SYMTAB, + SEC_STRTAB, + SEC_SHSTRTAB, + SEC_CNT, +}; + +enum { + SHSTR_TEXT = 1, + SHSTR_REL_TEXT = SHSTR_TEXT + sizeof(".text"), + SHSTR_SYMTAB = SHSTR_REL_TEXT + sizeof(".rel.text"), + SHSTR_STRTAB = SHSTR_SYMTAB + sizeof(".symtab"), + SHSTR_SHSTRTAB = SHSTR_STRTAB + sizeof(".strtab"), +}; + +struct test_elf { + void *buf; + size_t sz; +}; + +static size_t elf_round_up(size_t value, size_t align) +{ + return (value + align - 1) / align * align; +} + +/* + * Build a minimal ET_REL object in memory. A normal .bpf.c source cannot + * produce a relocation whose offset points past the relocated section, so the + * test constructs the ELF directly and feeds it to bpf_linker__add_buf(). + */ +static struct test_elf make_relo_obj(size_t relo_off) +{ + static const char shstrtab[] = "\0.text\0.rel.text\0.symtab\0.strtab\0.shstrtab"; + static const char strtab[] = "\0"; + struct bpf_insn insns[] = { + { + .code = BPF_ALU64 | BPF_MOV | BPF_K, + .dst_reg = BPF_REG_0, + .imm = 0, + }, + { + .code = BPF_JMP | BPF_EXIT, + }, + }; + size_t off, text_off, rel_off, symtab_off, strtab_off, shstrtab_off, shdr_off; + struct test_elf obj = {}; + Elf64_Shdr *shdr; + Elf64_Ehdr *ehdr; + Elf64_Sym *sym; + Elf64_Rel *rel; + + off = sizeof(*ehdr); + text_off = elf_round_up(off, 8); + off = text_off + sizeof(insns); + rel_off = elf_round_up(off, 8); + off = rel_off + sizeof(*rel); + symtab_off = elf_round_up(off, 8); + off = symtab_off + 2 * sizeof(*sym); + strtab_off = off; + off = strtab_off + sizeof(strtab); + shstrtab_off = off; + off = shstrtab_off + sizeof(shstrtab); + shdr_off = elf_round_up(off, 8); + off = shdr_off + SEC_CNT * sizeof(*shdr); + + obj.buf = calloc(1, off); + if (!obj.buf) + return obj; + obj.sz = off; + + ehdr = obj.buf; + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); + ehdr->e_ident[EI_CLASS] = ELFCLASS64; + ehdr->e_ident[EI_DATA] = ELFDATA2LSB; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_type = ET_REL; + ehdr->e_machine = EM_BPF; + ehdr->e_version = EV_CURRENT; + ehdr->e_ehsize = sizeof(*ehdr); + ehdr->e_shoff = shdr_off; + ehdr->e_shentsize = sizeof(*shdr); + ehdr->e_shnum = SEC_CNT; + ehdr->e_shstrndx = SEC_SHSTRTAB; + + memcpy(obj.buf + text_off, insns, sizeof(insns)); + + rel = obj.buf + rel_off; + rel->r_offset = relo_off; + rel->r_info = ELF64_R_INFO(1, R_BPF_64_64); + + sym = obj.buf + symtab_off; + sym[1].st_info = ELF64_ST_INFO(STB_LOCAL, STT_SECTION); + sym[1].st_shndx = SEC_TEXT; + + memcpy(obj.buf + strtab_off, strtab, sizeof(strtab)); + memcpy(obj.buf + shstrtab_off, shstrtab, sizeof(shstrtab)); + + shdr = obj.buf + shdr_off; + shdr[SEC_TEXT] = (Elf64_Shdr) { + .sh_name = SHSTR_TEXT, + .sh_type = SHT_PROGBITS, + .sh_flags = SHF_ALLOC | SHF_EXECINSTR, + .sh_offset = text_off, + .sh_size = sizeof(insns), + .sh_addralign = 8, + .sh_entsize = sizeof(struct bpf_insn), + }; + shdr[SEC_REL_TEXT] = (Elf64_Shdr) { + .sh_name = SHSTR_REL_TEXT, + .sh_type = SHT_REL, + .sh_offset = rel_off, + .sh_size = sizeof(*rel), + .sh_link = SEC_SYMTAB, + .sh_info = SEC_TEXT, + .sh_addralign = 8, + .sh_entsize = sizeof(*rel), + }; + shdr[SEC_SYMTAB] = (Elf64_Shdr) { + .sh_name = SHSTR_SYMTAB, + .sh_type = SHT_SYMTAB, + .sh_offset = symtab_off, + .sh_size = 2 * sizeof(*sym), + .sh_link = SEC_STRTAB, + .sh_info = 2, + .sh_addralign = 8, + .sh_entsize = sizeof(*sym), + }; + shdr[SEC_STRTAB] = (Elf64_Shdr) { + .sh_name = SHSTR_STRTAB, + .sh_type = SHT_STRTAB, + .sh_offset = strtab_off, + .sh_size = sizeof(strtab), + .sh_addralign = 1, + }; + shdr[SEC_SHSTRTAB] = (Elf64_Shdr) { + .sh_name = SHSTR_SHSTRTAB, + .sh_type = SHT_STRTAB, + .sh_offset = shstrtab_off, + .sh_size = sizeof(shstrtab), + .sh_addralign = 1, + }; + + return obj; +} + +static int link_relo_obj(size_t relo_off) +{ + char path[] = "/tmp/libbpf_linker_relo_XXXXXX"; + struct test_elf obj = {}; + struct bpf_linker *linker; + int err, fd; + + fd = mkstemp(path); + if (!ASSERT_OK_FD(fd, "mkstemp")) + return -errno; + close(fd); + + linker = bpf_linker__new(path, NULL); + if (!ASSERT_OK_PTR(linker, "linker_new")) { + err = libbpf_get_error(linker); + goto out_unlink; + } + + obj = make_relo_obj(relo_off); + if (!ASSERT_OK_PTR(obj.buf, "make_relo_obj")) { + err = -ENOMEM; + goto out_free_linker; + } + + err = bpf_linker__add_buf(linker, obj.buf, obj.sz, NULL); + if (!err) + err = bpf_linker__finalize(linker); + + free(obj.buf); +out_free_linker: + bpf_linker__free(linker); +out_unlink: + unlink(path); + return err; +} + +static void test_valid_relo_offset(void) +{ + ASSERT_OK(link_relo_obj(0), "valid_relo_offset"); +} + +static void test_invalid_relo_offset(void) +{ + ASSERT_EQ(link_relo_obj(0x1000), -EINVAL, "invalid_relo_offset"); +} + +void test_libbpf_linker(void) +{ + if (test__start_subtest("valid_relo_offset")) + test_valid_relo_offset(); + if (test__start_subtest("invalid_relo_offset")) + test_invalid_relo_offset(); +} -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset 2026-06-14 5:39 ` [PATCH bpf 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset HyeongJun An @ 2026-06-14 5:53 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-06-14 5:53 UTC (permalink / raw) To: HyeongJun An; +Cc: bpf Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] The dynamically generated ELF file hardcodes little-endian byte order (`ELFDATA2LSB`), which breaks the test on big-endian architectures. - [Low] Build failure due to undeclared `R_BPF_64_64` (and potentially `EM_BPF`) on systems with older standard headers. -- commit ea0de1dc0625c22a9ceaf6b346bc7dcde8b649da Author: HyeongJun An <sammiee5311@gmail.com> selftests/bpf: Test linker rejects out-of-range relocation offset Add regression coverage for libbpf static linker relocation offset checks. Build a minimal ET_REL/EM_BPF object in memory with a 16-byte executable section and one relocation against that section. > diff --git a/tools/testing/selftests/bpf/prog_tests/libbpf_linker.c b/tools/testing/selftests/bpf/prog_tests/libbpf_linker.c > new file mode 100644 > index 0000000000000..e7bec24343498 > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/libbpf_linker.c [ ... ] > + ehdr = obj.buf; > + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); > + ehdr->e_ident[EI_CLASS] = ELFCLASS64; > + ehdr->e_ident[EI_DATA] = ELFDATA2LSB; [Severity: Medium] Does hardcoding ELFDATA2LSB here break the test on big-endian architectures? Since the ELF header and structures are initialized using native host byte-order C struct assignments (like ehdr->e_type = ET_REL), libelf will see ELFDATA2LSB on big-endian systems (like s390x) and incorrectly byte-swap the already native big-endian fields. This could cause the linker to reject the ELF file with -EOPNOTSUPP. Should EI_DATA be set dynamically to match the host's byte order? > + ehdr->e_ident[EI_VERSION] = EV_CURRENT; > + ehdr->e_type = ET_REL; > + ehdr->e_machine = EM_BPF; [Severity: Low] Could this cause a build failure on systems with older standard headers? EM_BPF might be undeclared on extremely old systems. > + ehdr->e_version = EV_CURRENT; > + ehdr->e_ehsize = sizeof(*ehdr); > + ehdr->e_shoff = shdr_off; > + ehdr->e_shentsize = sizeof(*shdr); > + ehdr->e_shnum = SEC_CNT; > + ehdr->e_shstrndx = SEC_SHSTRTAB; > + > + memcpy(obj.buf + text_off, insns, sizeof(insns)); > + > + rel = obj.buf + rel_off; > + rel->r_offset = relo_off; > + rel->r_info = ELF64_R_INFO(1, R_BPF_64_64); [Severity: Low] Will this fail to compile on systems with older standard headers? R_BPF_64_64 was only added in glibc 2.32 (August 2020). On systems with older headers, such as Ubuntu 20.04 commonly used in CI environments, R_BPF_64_64 is undeclared. libbpf works around this by providing fallback definitions in libbpf_internal.h, but libbpf_linker.c doesn't include it. Could we add a fallback definition for R_BPF_64_64 in this test to ensure it builds on older environments? [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260614053927.160566-1-sammiee5311@gmail.com?part=2 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-06-14 7:50 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-14 5:39 [PATCH bpf 0/2] libbpf: Reject out-of-range linker relocation offsets HyeongJun An 2026-06-14 5:39 ` [PATCH bpf 1/2] " HyeongJun An 2026-06-14 7:50 ` sashiko-bot 2026-06-14 5:39 ` [PATCH bpf 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset HyeongJun An 2026-06-14 5:53 ` sashiko-bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.