[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