* [PATCH bpf v2 0/2] libbpf: Reject out-of-range linker relocation offsets
@ 2026-06-14 9:26 HyeongJun An
2026-06-14 9:26 ` [PATCH bpf v2 1/2] " HyeongJun An
2026-06-14 9:26 ` [PATCH bpf v2 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset HyeongJun An
0 siblings, 2 replies; 4+ messages in thread
From: HyeongJun An @ 2026-06-14 9:26 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.
Changes in v2:
- selftest: set the generated ELF object's EI_DATA from the host byte
order instead of hardcoding little-endian, so it works on big-endian
hosts (e.g. s390x).
- selftest: add fallback definitions for EM_BPF and R_BPF_64_64 for
older system headers.
Patch 1 (the fix) is unchanged.
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 | 231 ++++++++++++++++++
2 files changed, 237 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/libbpf_linker.c
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH bpf v2 1/2] libbpf: Reject out-of-range linker relocation offsets
2026-06-14 9:26 [PATCH bpf v2 0/2] libbpf: Reject out-of-range linker relocation offsets HyeongJun An
@ 2026-06-14 9:26 ` HyeongJun An
2026-06-15 0:05 ` Alexei Starovoitov
2026-06-14 9:26 ` [PATCH bpf v2 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset HyeongJun An
1 sibling, 1 reply; 4+ messages in thread
From: HyeongJun An @ 2026-06-14 9:26 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] 4+ messages in thread
* [PATCH bpf v2 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset
2026-06-14 9:26 [PATCH bpf v2 0/2] libbpf: Reject out-of-range linker relocation offsets HyeongJun An
2026-06-14 9:26 ` [PATCH bpf v2 1/2] " HyeongJun An
@ 2026-06-14 9:26 ` HyeongJun An
1 sibling, 0 replies; 4+ messages in thread
From: HyeongJun An @ 2026-06-14 9:26 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 | 231 ++++++++++++++++++
1 file changed, 231 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..a0a61b32fe2e
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/libbpf_linker.c
@@ -0,0 +1,231 @@
+// 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>
+
+#ifndef EM_BPF
+#define EM_BPF 247
+#endif
+
+#ifndef R_BPF_64_64
+#define R_BPF_64_64 1
+#endif
+
+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;
+}
+
+static unsigned char elf_byteorder(void)
+{
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ return ELFDATA2LSB;
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ return ELFDATA2MSB;
+#else
+#error "Unrecognized __BYTE_ORDER__"
+#endif
+}
+
+/*
+ * 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] = elf_byteorder();
+ 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] 4+ messages in thread
* Re: [PATCH bpf v2 1/2] libbpf: Reject out-of-range linker relocation offsets
2026-06-14 9:26 ` [PATCH bpf v2 1/2] " HyeongJun An
@ 2026-06-15 0:05 ` Alexei Starovoitov
0 siblings, 0 replies; 4+ messages in thread
From: Alexei Starovoitov @ 2026-06-15 0:05 UTC (permalink / raw)
To: HyeongJun An, 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, stable
On Sun Jun 14, 2026 at 2:26 AM PDT, HyeongJun An wrote:
> 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:
libbpf trusts ELF.
There are many way to crash libbpf and libelf, for that matter, with corrupted ELF.
Please don't send such hardening patches.
pw-bot: cr
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-15 0:05 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-14 9:26 [PATCH bpf v2 0/2] libbpf: Reject out-of-range linker relocation offsets HyeongJun An
2026-06-14 9:26 ` [PATCH bpf v2 1/2] " HyeongJun An
2026-06-15 0:05 ` Alexei Starovoitov
2026-06-14 9:26 ` [PATCH bpf v2 2/2] selftests/bpf: Test linker rejects out-of-range relocation offset HyeongJun An
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox