[BUG] libbpf: OOB access when relocating a terminal LDIMM64

Mingpei CAO <[email protected]>
Newsgroups org.kernel.vger.bpf
Message-ID <CABzjXVyUkn+_1QNM6WmMeuNUbAcQEUtL=feABs6wWooRs2--5Q@mail.gmail.com>
Hello,

Resending as plain text because the previous message was rejected
by the bpf mailing list due to an HTML MIME part.

I found an out-of-bounds access in libbpf's CO-RE relocation handling. A
malformed BPF ELF object can place the first half of an LDIMM64 instruction
in the program's final instruction slot and direct a CO-RE relocation to
it. libbpf validates the relocation's starting instruction index, but does
not verify that the required second LDIMM64 slot exists before accessing
insn[1].

The affected path is:
    bpf_object__relocate_core() -> bpf_core_patch_insn()
This affects both normal relocation and relocation poisoning.

AI assistance was used in preparing this report. I independently reviewed
the source, traced the affected versions, and reproduced both the
out-of-bounds read and write with AddressSanitizer. Reproducer files are
available on request.

Affected versions
-----------------

The poisoning write is present starting in libbpf v0.2. The normal
out-of-bounds read is present starting in v0.5, and both remain present
through v1.7.0 and current master:
    f7081a6baf3f54949aacb8c2fc11bb30783b83e9
Commit f81dbd3475c4 removed the previous check for:
    insn_idx + 1 >= prog->insns_cnt
which made the normal LDIMM64 relocation path susceptible to the
out-of-bounds read.
Thus, the normal read path is affected from v0.5 onward, while the
poisoning write path predates it.

Related public discussion
-------------------------

I found a related 2021 fix:
https://lore.kernel.org/bpf/[email protected]/
That change corrected the CO-RE relocation index check from '>' to '>=',
but it validates only the first instruction slot. It does not reject a
terminal LDIMM64 instruction whose required second slot is missing.

I therefore do not believe this issue is a duplicate.

Trigger conditions
------------------

A malformed BPF ELF object must contain a program whose final eight-byte
instruction slot starts an LDIMM64 instruction but has no second slot. A
.BTF.ext CO-RE relocation must target that instruction.
The invalid access occurs during user-space relocation, before the
malformed program reaches the kernel.

Root cause
----------

bpf_object__init_prog() allocates exactly the size of the instruction
section at src/libbpf.c:892.

bpf_object__relocate_core() validates only:
    if (insn_idx >= prog->insns_cnt)
        return -EINVAL;
It then passes:
    &prog->insns[insn_idx]
to bpf_core_patch_insn().

For LDIMM64, the normal relocation path reads insn[1] at
src/relo_core.c:1136 and may write insn[1].imm at line 1153.

The poisoning path passes insn + 1 to bpf_core_poison_insn() at
src/relo_core.c:1056, which writes through that pointer at line 991.

Neither path verifies that:
    insn_idx + 1 < prog->insns_cnt

Observed results
----------------

Using GCC 13 and ASan on current libbpf in an Ubuntu 24.04 ARM64
container, with an exact 64-byte instruction allocation, the normal
relocation input produced:

    ERROR: AddressSanitizer: heap-buffer-overflow
    READ of size 4
        #0 bpf_core_patch_insn src/relo_core.c:1136
        #1 bpf_object__relocate_core src/libbpf.c:6178
    0 bytes after 64-byte region
    allocated by bpf_object__init_prog src/libbpf.c:892

The relocation-poisoning input produced:

    ERROR: AddressSanitizer: heap-buffer-overflow
    WRITE of size 1
        #0 bpf_core_poison_insn src/relo_core.c:991
        #1 bpf_core_patch_insn src/relo_core.c:1056
        #2 bpf_object__relocate_core src/libbpf.c:6178
    0 bytes after 64-byte region
    allocated by bpf_object__init_prog src/libbpf.c:892

As controls, current cilium/ebpf rejects the normal relocation input with:
    64bit immediate is missing second half
while current Aya returns:
    InvalidInstructionIndex

Impact
------

The confirmed impact is a heap out-of-bounds read on the normal relocation
path and a heap out-of-bounds write on the poisoning path.

ASan aborts on the first invalid access. I have not demonstrated
exploitation in a production build, memory disclosure, code execution,
privilege escalation, or kernel compromise.

Proposed fix
------------

Reject an LDIMM64 instruction when its second slot lies outside the
program, for example:

    if (is_ldimm64_insn(insn) &&
        insn_idx + 1 >= prog->insns_cnt)
        return -EINVAL;

The check should cover both normal relocation and relocation poisoning.

I applied this guard to the assessed commit and reran both ASan
reproducers. Both completed without sanitizer findings.

A regression test should cover both malformed inputs, together with a
valid two-slot LDIMM64 control.

I can provide the ELF files, full ASan logs, or help test an upstream
patch.

Regards,
Mingpei
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.