17 September 2022
WIP - BPF and OSS-Fuzz - Understanding a bug
by Anne Macedo
I came across this bug [1] on libbpf and I decided to tackle this. Itβs a bug that was reported by OSS-Fuzz, a project created by Google to lend their computing power to do Fuzzing on open source software.
Reproducing the bug is straightforward, but I have to break it up to understand it better.
Basically, you have to clone the libbpf repo (keep in mind that this is a mirror from tools/lib/bpf from the kernel). Then, build, download the test case program, and run the fuzzer
./scripts/build-fuzzers.sh
wget -O oss-fuzz-42345 https://oss-fuzz.com/download?testcase_id= 5041748798210048
./out/bpf-object-fuzzer oss-fuzz-42345
You can see the stacktrace on the bug description. [1]
Anyways, it does tell me that thereβs a bug on bpf_object__init_user_btf_map, but that doesnβt really tell me which data was loaded, what is the state of the registers when we get a SEGV, etc. I saw a similar bug on [2] that had a test code, so I kindly asked the issue creator to help me with that.
They responded with some code and now I understand how to reproduce it.
C code for reproducing the bug
Getting the code consists in:
dumping the test case bytes (Iβll explain later)
including the correct header files
opening the test case (which is a bpf program)
To dump the test case bytes:
xxd -i oss-fuzz-42345
This will print some C code that contains the raw bytes.
Now, the rest of the code:
#include "bpf/btf.h"
#include "bpf/libbpf.h"
// Code for the test case dump redacted for readability
static int libbpf_print_fn ( enum libbpf_print_level level , const char * format , va_list args )
{
return 0 ;
}
int main ( int argc , char * argv []) {
struct bpf_object * obj = NULL ;
DECLARE_LIBBPF_OPTS ( bpf_object_open_opts , opts );
int err ;
libbpf_set_print ( libbpf_print_fn );
opts . object_name = "fuzz-object" ;
obj = bpf_object__open_mem ( oss_fuzz_42345 , sizeof ( oss_fuzz_42345 ), & opts );
err = libbpf_get_error ( obj );
if ( err )
return 0 ;
bpf_object__close ( obj );
return 0 ;
}
I then compiled the code:
gcc -fsanitize = address -g -O1 -fno-omit-frame-pointer oss-fuzz-42345.c -o oss-fuzz-42345.o -I ../libbpf/src -I ../libbpf/include/uapi ../libbpf/src/libbpf.a -lelf -lz
When I run it:
./oss-fuzz-42345.o
AddressSanitizer:DEADLYSIGNAL
=================================================================
==49276==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x55f9ffa87295 bp 0x7ffca3fee1b0 sp 0x7ffca3fecf00 T0)
==49276==The signal is caused by a READ memory access.
==49276==Hint: address points to the zero page.
#0 0x55f9ffa87295 in bpf_object__init_user_btf_map :2450
#1 0x55f9ffa87295 in bpf_object__init_user_btf_maps :2574
#2 0x55f9ffa87295 in bpf_object__init_maps :2595
#3 0x55f9ffa4d900 in bpf_object_open :7207
#4 0x55f9ffa4df0d in bpf_object__open_mem :7244
#5 0x55f9ffa46a61 in main /home/kernel-dev/oss-fuzz-42345/oss-fuzz-42345.c:240
#6 0x7f06de11ed09 in __libc_start_main ../csu/libc-start.c:308
#7 0x55f9ffa468a9 in _start (/home/kernel-dev/oss-fuzz-42345/oss-fuzz-42345.o+0x4f8a9)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV :2450 in bpf_object__init_user_btf_map
==49276==ABORTING
Debugging the code
Debugging is easy with gdb. To see the whole code, I had to pass the directory of my libbpf.
gdb ./oss-fuzz-42345.o
(gdb) dir /home/kernel-dev/libbpf/src
Source directories searched: /home/kernel-dev/libbpf/src:$cdir:$cwd
(gdb) run
Starting program: /home/kernel-dev/oss-fuzz-42345/oss-fuzz-42345.o
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
0x00005555555e4295 in bpf_object__init_user_btf_map (obj=0x614000000040, var_idx=0, sec_idx=1, pin_root_path=0x0, sec=<optimized out>, data=<optimized out>, strict=<optimized out>)
at libbpf.c:2450
2450 map_name = btf__name_by_offset(obj->btf, var->name_off);
(gdb) l
2445 int err;
2446
2447 vi = btf_var_secinfos(sec) + var_idx;
2448 var = btf__type_by_id(obj->btf, vi->type);
2449 var_extra = btf_var(var);
2450 map_name = btf__name_by_offset(obj->btf, var->name_off);
2451
2452 if (map_name == NULL || map_name[0] == '\0') {
2453 pr_warn("map #%d: empty name.\n", var_idx);
2454 return -EINVAL;
(gdb) break 2450
Breakpoint 1 at 0x5555555e426e: file libbpf.c, line 2450.
(gdb) p var
$1 = (const struct btf_type *) 0x0
(gdb) p var->name_off
Cannot access memory at address 0x0
This must be the culprit: var->name_off. varβs address is 0x0 and indirectly reaching var->name_off will cause a segfault. A null pointer dereference.
By changing the var, we get different values on the SEGV.
(gdb) p var
$2 = (const struct btf_type *) 0xff
(gdb) p var->name_off
Cannot access memory at address 0xff
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x00005555555e4295 in bpf_object__init_user_btf_map (obj=0x614000000040, var_idx=0, sec_idx=1, pin_root_path=0x0, sec=<optimized out>, data=<optimized out>, strict=<optimized out>)
at libbpf.c:2450
2450 map_name = btf__name_by_offset(obj->btf, var->name_off);
(gdb) c
Continuing.
AddressSanitizer:DEADLYSIGNAL
=================================================================
==49367==ERROR: AddressSanitizer: SEGV on unknown address 0x0000000000ff (pc 0x5555555e4295 bp 0x7fffffffdf50 sp 0x7fffffffcca0 T0)
But where does this var->name_off come from?
Weβll try to figure out on the next session.
Disassembling the test case
We have two programs: a userspace program (the C code we created up above) and the bpf program (that bunch of bytecode). In order to understand the bpf program, we need to disassemble it somehow. I tried analysing it with all the tools I had available, to no avail.
qemu β oss-fuzz-42345 file oss-fuzz-42345
oss-fuzz-42345: ELF 64-bit LSB relocatable, eBPF,, corrupted section header size
qemu β oss-fuzz-42345 binwalk oss-fuzz-42345
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
qemu β oss-fuzz-42345 llvm-objdump -d oss-fuzz-42345
llvm-objdump: error: 'oss-fuzz-42345' : invalid e_shentsize in ELF header: 8224
qemu β oss-fuzz-42345 rabin2 -S oss-fuzz-42345
[ Sections]
nth paddr size vaddr vsize perm name
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
0 0x2020202020202020 0xff20202020202020 0x2020202028202020 0xff20202020202020 ---- invalid0
1 0x2020202020202020 0x0 0x2020202028202020 0x0 ---- .maps
2 0x00000020 0x20 0x08000020 0x20 ---- _134217760
3 0x00000020 0x20 0x08000020 0x20 ---- ? ? ? .BTF
4 0x00000020 0x20 0x08000020 0x20 ---- ? ? ? .BTF
5 0x00000020 0x20 0x08000020 0x20 ---- ? ? ? .BTF
6 0x000000d4 0x2ff 0x080000d4 0x2ff ---- .BTF
7 0x00000320 0x20 0x08000320 0x20 ---- _134218528_4
8 0x00000020 0x20 0x08000020 0x20 ---- ? ? ? .BTF
9 0x00000320 0xc0 0x08000320 0xc0 ---- ? ? ? .BTF
10 0x00000420 0x20 0x08000420 0x20 ---- _134218784_7
11 0x2020202020202020 0x0 0x2020202028202020 0x0 ---- _2314885530952671264_8
12 0x2020202020202020 0x0 0x2020202028202020 0x0 ---- ? ? ? .BTF
13 0x2020202020202020 0x0 0x2020202028202020 0x0 -rwx ? ? ? .BTF
14 0x00000020 0x420 0x08000020 0x420 ---- ? ? ? .BTF
15 0x0000058b 0xff 0x0800058b 0xff ---- _134219147_12
qemu β oss-fuzz-42345 llvm-objdump -d oss-fuzz-42345
llvm-objdump: error: 'oss-fuzz-42345' : invalid e_shentsize in ELF header: 8224
qemu β oss-fuzz-42345 bpftool prog load -L -d oss-fuzz-42345
libbpf: loading oss-fuzz-42345
libbpf: elf: section( 1) .maps, size 0, link -14671840 , flags 2020202020202020, type = 538976288
libbpf: elf: section( 2) ? ? ? .BTF, size 32, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 2) ? ? ? .BTF ( size 32)
libbpf: elf: section( 3) ? ? ? .BTF, size 32, link 538976288, flags 2020202020202020, type = -14671840
libbpf: elf: skipping section( 3) ? ? ? .BTF ( size 32)
libbpf: elf: section( 4) ? ? ? .BTF, size 32, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 4) ? ? ? .BTF ( size 32)
libbpf: elf: section( 5) ? ? ? .BTF, size 32, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 5) ? ? ? .BTF ( size 32)
libbpf: elf: section( 6) .BTF, size 767, link 538976288, flags 2020202020202020, type = 1
libbpf: elf: section( 7) ? ? ? .BTF, size 32, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 7) ? ? ? .BTF ( size 32)
libbpf: elf: section( 8) ? ? ? .BTF, size 32, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 8) ? ? ? .BTF ( size 32)
libbpf: elf: section( 9) ? ? ? .BTF, size 192, link 15, flags 2020202020202020, type = 2
libbpf: elf: section( 10) ? ? ? .BTF, size 32, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 10) ? ? ? .BTF ( size 32)
libbpf: elf: section( 11) ? ? ? .BTF, size 0, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 11) ? ? ? .BTF ( size 0)
libbpf: elf: section( 12) ? ? ? .BTF, size 0, link 538976288, flags 2020202020ffff20, type = 538976511
libbpf: elf: skipping section( 12) ? ? ? .BTF ( size 0)
libbpf: elf: section( 13) ? ? ? .BTF, size 0, link 538976288, flags 202020ffffffffff, type = -57312
libbpf: elf: skipping section( 13) ? ? ? .BTF ( size 0)
libbpf: elf: section( 14) ? ? ? .BTF, size 1056, link 538976288, flags 2020202020202020, type = 538976288
libbpf: elf: skipping section( 14) ? ? ? .BTF ( size 1056)
libbpf: looking for externs among 8 symbols...
libbpf: collected 0 externs total
[ 1] 49558 segmentation fault bpftool prog load -L -d oss-fuzz-42345
qemu β oss-fuzz-42345 readelf -a oss-fuzz-42345
ELF Header:
Magic: 7f 45 4c 46 02 01 01 20 20 20 20 20 20 20 20 20
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: <unknown: 20>
ABI Version: 32
Type: REL (Relocatable file)
Machine: Linux BPF
Version: 0x20202020
Entry point address: 0x2020202020202020
Start of program headers: 2314885530818453536 (bytes into file)
Start of section headers: 1600 (bytes into file)
Flags: 0x20202020
Size of this header: 8224 (bytes)
Size of program headers: 8224 (bytes)
Number of program headers: 8224
Size of section headers: 8224 (bytes)
Number of section headers: 16
Section header string table index: 15
readelf: Warning: The e_shentsize field in the ELF header is larger than the size of an ELF section header
readelf: Error: Reading 131584 bytes extends past end of file for section headers
readelf: Error: Section headers are not available!
readelf: Error: Too many program headers - 0x2020 - the file is not that big
There is no dynamic section in this file.
readelf: Error: Too many program headers - 0x2020 - the file is not that big
Fixing the header!
qemu β oss-fuzz-42345 r2 -w -nn oss-fuzz-42345
-- Can you stand on your head ?
[ 0x00000000]> .pf.elf_header.shentsize= 0x000000040
[ 0x00000000]> q
qemu β oss-fuzz-42345 file oss-fuzz-42345
oss-fuzz-42345: ELF 64-bit LSB relocatable, eBPF,, not stripped
How detrimental it is
Now that we know everything about this flaw, you may be wondering βwhy is this important?β. Weβll investigate this here.
References
[1]libbpf #484
[2]libbpf #390
[]
tags: