From 52168d53f933ceb6c881edaa01be771bdf0944f8 Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Fri, 29 May 2026 13:13:23 +0200 Subject: [PATCH 1/5] elf_reader.go: Check missing BTF in struct_ops reloc logic Struct_ops programs need BTF, but when an ELF is presented without BTF, the struct ops reloc logic would panic. Adding a nil check and throwing a clear error when BTF is missing when its required. Signed-off-by: Dylan Reimerink --- elf_reader.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/elf_reader.go b/elf_reader.go index f8f7824e9..3b2b7cdb1 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -1487,6 +1487,10 @@ func (ec *elfCode) associateStructOpsRelocs(progs map[string]*ProgramSpec) error return fmt.Errorf("failed to read section data: %w", err) } + if ec.btf == nil { + return fmt.Errorf("struct_ops section %s: missing BTF", sec.Name) + } + // Resolve the BTF datasec describing variables in this section. var ds *btf.Datasec if err := ec.btf.TypeByName(sec.Name, &ds); err != nil { From 3d86085aaf9e66a8c03c440cd293e50335269221 Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Fri, 29 May 2026 13:20:38 +0200 Subject: [PATCH 2/5] elf_reader.go: Catch compressed ELF sections When ELF sections are compressed the debug/elf package does not populate the `ReaderAt` field of the `elf.Section` struct. When we do not check for its presence, we end up with a nil pointer dereference when trying to read the section data. So check for the presence of the `ReaderAt` field and return an error if it is nil. We do not support compressed ELF sections. Signed-off-by: Dylan Reimerink --- elf_reader.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/elf_reader.go b/elf_reader.go index 3b2b7cdb1..34f52609b 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -382,6 +382,10 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) { return nil, fmt.Errorf("section %v: missing symbols", sec.Name) } + if sec.ReaderAt == nil { + return nil, fmt.Errorf("compressed program section is not supported") + } + funcs, err := ec.loadFunctions(sec) if err != nil { return nil, fmt.Errorf("section %v: %w", sec.Name, err) @@ -803,6 +807,10 @@ func (ec *elfCode) loadMaps() error { return fmt.Errorf("section %v: no symbols", sec.Name) } + if sec.ReaderAt == nil { + return fmt.Errorf("compressed map section is not supported") + } + vars, err := ec.sectionVars(ec.btf, sec.Name) if err != nil { return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err) From 4b1e53e35358d14707fe7afd4119961d6e700686 Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Fri, 29 May 2026 13:23:00 +0200 Subject: [PATCH 3/5] ext_info: handle malformed line info rec number Part of the .BTF.ext header contains the number of line info records. We were blindly taking this number and allocating x mount of records in go. If this number is malformed it causes an out of memory error. So instead of pre-allocating memory for all the records, lets do so for 1024 at a time. If we run out of section data, we stop and return an error, going from an OOM to a more reasonable error. Signed-off-by: Dylan Reimerink --- btf/ext_info.go | 12 +++++++++--- btf/ext_info_test.go | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/btf/ext_info.go b/btf/ext_info.go index 7cd1dc51c..2e2aa629f 100644 --- a/btf/ext_info.go +++ b/btf/ext_info.go @@ -642,9 +642,15 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got) } - out := make([]bpfLineInfo, recordNum) - if err := binary.Read(r, bo, out); err != nil { - return nil, fmt.Errorf("can't read line info: %v", err) + out := make([]bpfLineInfo, 0) + chunk := make([]bpfLineInfo, min(1024, recordNum)) + for remaining := recordNum; remaining > 0; { + n := min(uint32(1024), remaining) + if err := binary.Read(r, bo, chunk[:n]); err != nil { + return nil, fmt.Errorf("can't read line info: %v", err) + } + out = append(out, chunk[:n]...) + remaining -= n } if offsetInBytes { diff --git a/btf/ext_info_test.go b/btf/ext_info_test.go index e791b9bb2..6c0783dc0 100644 --- a/btf/ext_info_test.go +++ b/btf/ext_info_test.go @@ -48,8 +48,8 @@ func TestParseLineInfoRecordsAllocations(t *testing.T) { parseLineInfoRecords(bytes.NewReader(buf), internal.NativeEndian, size, count, true) }) - // 7 is the number of allocations on go 1.22 + // 9 is the number of allocations on go 1.26 // what we want to test is that we are not allocating // once per record - qt.Assert(t, qt.IsTrue(allocs <= 7)) + qt.Assert(t, qt.IsTrue(allocs <= 9)) } From 109b4d0c3be3faa751a8cc683486080b0d088ba7 Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Fri, 29 May 2026 13:18:41 +0200 Subject: [PATCH 4/5] elf_reader.go: Add bounds checking for symbols in ELF section Fuzzing the ELF reader revealed that it can be made to read out of bounds data if a symbol's size extends beyond the end of its section. Adding a bounds check to throw a proper error instead of panicking. Limit symbol values and sizes to 32 bits to prevent 64 bit integer overflow. This limits the maximum size of a symbol to 4GB, which is more than enough. Signed-off-by: Dylan Reimerink --- elf_reader.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/elf_reader.go b/elf_reader.go index 34f52609b..ac0cdb15b 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -456,6 +456,13 @@ func (ec *elfCode) loadFunctions(sec *elfSection) (map[string]asm.Instructions, return nil, fmt.Errorf("duplicate symbol %s in section %s", sym.Name, sec.Name) } + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return nil, fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return nil, fmt.Errorf("symbol %s: size goes out of bounds of section %s", sym.Name, sec.Name) + } + // Decode the symbol's instruction stream, limited to its size. sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) insns := make(asm.Instructions, 0, sym.Size/asm.InstructionSize) @@ -822,6 +829,13 @@ func (ec *elfCode) loadMaps() error { return fmt.Errorf("duplicate symbol %s in section %s", name, sec.Name) } + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return fmt.Errorf("symbol %s: size goes out of bounds of section %s", name, sec.Name) + } + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) spec := MapSpec{ @@ -920,6 +934,13 @@ func (ec *elfCode) loadBTFMaps() error { return fmt.Errorf("section %v: missing symbol for map %s", sec.Name, name) } + if sym.Value > math.MaxUint32 || sym.Size > math.MaxUint32 { + return fmt.Errorf("symbol %s: offset or size exceeds 32 bits in section %s", sym.Name, sec.Name) + } + if sym.Value+sym.Size > sec.Size { + return fmt.Errorf("section %v: symbol %s: size goes out of bounds of section", sec.Name, name) + } + sr := internal.NewBufferedSectionReader(sec, int64(sym.Value), int64(sym.Size)) // The BTF metadata for each Var contains the full length of the map From 3b6288110e29489eb7d7d8f7e08ecd7c0357bb39 Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Fri, 29 May 2026 16:18:40 +0200 Subject: [PATCH 5/5] elf_reader: handle int overflow, div by zero, and compressed section A final round of fuzzing surfaced 3 more edge cases. First is an integer overflow, handled by limiting both integers to 32 bit so they do not overflow a 64 bit integer when compared. Second is a division by zero, handled by checking if the divisor is zero before performing the division. Third is a compressed section, which is not supported. After these the fuzzer surfaced nothing within an hour of fuzzing, so I consider this good enough for now. Signed-off-by: Dylan Reimerink --- elf_reader.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/elf_reader.go b/elf_reader.go index ac0cdb15b..e2f264912 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -911,6 +911,10 @@ func (ec *elfCode) loadBTFMaps() error { return fmt.Errorf("missing BTF") } + if sec.ReaderAt == nil { + return fmt.Errorf("compressed BTF map section is not supported") + } + vars, err := ec.sectionVars(ec.btf, sec.Name) if err != nil { return fmt.Errorf("section %v: loading map variable BTF: %w", sec.Name, err) @@ -1274,6 +1278,10 @@ func resolveBTFValuesContents(es *elfSection, sym elf.Symbol, member btf.Member) return nil, fmt.Errorf("member offset %d exceeds symbol size %d", member.Offset.Bytes(), sym.Size) } + if es.Addralign == 0 { + return nil, fmt.Errorf("section has no address alignment, can't resolve .values contents") + } + for i, sym := range valuesRelocations(es, sym, member) { // Emit a value stub based on the type of relocation to be replaced by a // real fd later in the pipeline before populating the Map. @@ -1383,6 +1391,10 @@ func (ec *elfCode) loadDataSections() error { return fmt.Errorf("data section %s: variable %s offset %d exceeds maximum", sec.Name, sym.Name, off) } + if sym.Size > math.MaxUint32 { + return fmt.Errorf("data section %s: variable %s size %d exceeds maximum", sec.Name, sym.Name, sym.Size) + } + ec.vars[sym.Name] = &VariableSpec{ SectionName: sec.Name, Name: sym.Name,