From b9d966433359aba9158254fb2752a3da817cbd8b Mon Sep 17 00:00:00 2001 From: Sonic Date: Tue, 30 Dec 2025 04:23:45 +0200 Subject: [PATCH 1/2] fix(F-D10): ELF-loader get_string_in_section method --- pkg/sbpf/loader/loader.go | 10 ++++++ pkg/sbpf/loader/parse.go | 52 +++++++++++++++++++++++---- pkg/sbpf/loader/parse_test.go | 68 +++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 pkg/sbpf/loader/parse_test.go diff --git a/pkg/sbpf/loader/loader.go b/pkg/sbpf/loader/loader.go index 916ccb73..79140c15 100644 --- a/pkg/sbpf/loader/loader.go +++ b/pkg/sbpf/loader/loader.go @@ -17,6 +17,16 @@ import ( ) var ErrOutOfBounds = errors.New("value out of bounds") +var ErrInvalidSectionHeader = errors.New("invalid section header") + +type ErrStringTooLong struct { + Name string + Len uint64 +} + +func (e *ErrStringTooLong) Error() string { + return fmt.Sprintf("Section or symbol name `%s` is longer than `%d` bytes", e.Name, e.Len) +} // TODO Fuzz // TODO Differential fuzz against rbpf diff --git a/pkg/sbpf/loader/parse.go b/pkg/sbpf/loader/parse.go index 4d1767d7..12e8cdf5 100644 --- a/pkg/sbpf/loader/parse.go +++ b/pkg/sbpf/loader/parse.go @@ -4,6 +4,7 @@ import ( "bufio" "debug/elf" "encoding/binary" + "errors" "fmt" "io" "math" @@ -291,26 +292,63 @@ func (l *Loader) readSectionHeaderTable() error { return iter.Err() } +// Query a single string from a section which is marked as SHT_STRTAB +// +// See https://github.com/anza-xyz/sbpf/blob/main/src/elf_parser/mod.rs#L468 func (l *Loader) getString(strtab *elf.Section64, stroff uint32, maxLen uint16) (string, error) { if elf.SectionType(strtab.Type) != elf.SHT_STRTAB { - return "", fmt.Errorf("invalid strtab") + return "", ErrInvalidSectionHeader + } + + offset, carry := bits.Add64(strtab.Off, uint64(stroff), 0) + if carry != 0 { + return "", ErrOutOfBounds + } + + sectionEnd, carry := bits.Add64(strtab.Off, strtab.Size, 0) + if carry != 0 { + return "", ErrOutOfBounds + } + + maxEnd, carry := bits.Add64(offset, uint64(maxLen), 0) + if carry != 0 { + return "", ErrOutOfBounds + } + + if sectionEnd < maxEnd { + maxEnd = sectionEnd + } + + if offset > l.fileSize { + return "", ErrOutOfBounds + } + + readLen := maxEnd - offset + if maxEnd > l.fileSize { + readLen = l.fileSize - offset } - offset := strtab.Off + uint64(stroff) - if offset > l.fileSize || offset+uint64(maxLen) > l.fileSize { - return "", io.ErrUnexpectedEOF + if readLen == 0 { + return "", ErrOutOfBounds } - rd := bufio.NewReader(io.NewSectionReader(l.rd, int64(offset), int64(maxLen))) + + rd := bufio.NewReader(io.NewSectionReader(l.rd, int64(offset), int64(readLen))) var builder strings.Builder for { b, err := rd.ReadByte() if err != nil { - return "", err + switch { + case errors.Is(err, io.EOF): + return "", &ErrStringTooLong{Name: builder.String(), Len: readLen} + default: + return "", err + } } - if b == 0 { + if b == 0x00 { break } builder.WriteByte(b) } + return builder.String(), nil } diff --git a/pkg/sbpf/loader/parse_test.go b/pkg/sbpf/loader/parse_test.go new file mode 100644 index 00000000..9694bea5 --- /dev/null +++ b/pkg/sbpf/loader/parse_test.go @@ -0,0 +1,68 @@ +package loader + +import ( + "debug/elf" + "testing" +) + +func TestLoader_getString(t *testing.T) { + tests := map[string]struct { + buf []byte + strtab *elf.Section64 + stroff uint32 + maxLen uint16 + want string + wantErr error + }{ + "valid string in section": { + buf: []byte(".text\x00"), + strtab: &elf.Section64{Off: 0, Size: 6, Type: uint32(elf.SHT_STRTAB)}, + stroff: 0, + maxLen: 16, + want: ".text", + wantErr: nil, + }, + "invalid section header": { + buf: []byte(".text\x00"), + strtab: &elf.Section64{Off: 0, Size: 6}, + stroff: 0, + maxLen: 16, + want: "", + wantErr: ErrInvalidSectionHeader, + }, + "out of bounds": { + buf: []byte(".text\x00"), + strtab: &elf.Section64{Off: 6, Size: 6, Type: uint32(elf.SHT_STRTAB)}, + stroff: 0, + maxLen: 16, + want: "", + wantErr: ErrOutOfBounds, + }, + "section header too long": { + buf: []byte(".data.rel.ro\x00"), + strtab: &elf.Section64{Off: 0, Size: 6, Type: uint32(elf.SHT_STRTAB)}, + stroff: 0, + maxLen: 16, + want: "", + wantErr: &ErrStringTooLong{Name: ".data.", Len: 6}, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + l, err := NewLoaderFromBytes(tt.buf) + if err != nil { + t.Fatalf("could not construct receiver type: %v", err) + } + got, gotErr := l.getString(tt.strtab, tt.stroff, tt.maxLen) + + if gotErr != nil && gotErr.Error() != tt.wantErr.Error() { + t.Errorf("getString() failed: %+v", gotErr) + return + } + + if got != tt.want { + t.Errorf("getString() = %v, want %v", got, tt.want) + } + }) + } +} From f0b8896517aaae3e2d95fbdc7d4a3ed458877b0e Mon Sep 17 00:00:00 2001 From: Sonic Date: Fri, 2 Jan 2026 17:14:21 +0200 Subject: [PATCH 2/2] refactor: move sbpf-related errors into sbpf pkg --- pkg/sbpf/asm.go | 6 +++--- pkg/sbpf/errors.go | 27 +++++++++++++++++++++++++++ pkg/sbpf/loader/loader.go | 13 ------------- pkg/sbpf/loader/parse.go | 17 +++++++++-------- pkg/sbpf/loader/parse_test.go | 8 +++++--- pkg/sbpf/vm.go | 12 ------------ 6 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 pkg/sbpf/errors.go diff --git a/pkg/sbpf/asm.go b/pkg/sbpf/asm.go index f92f421c..6ad5f4b2 100644 --- a/pkg/sbpf/asm.go +++ b/pkg/sbpf/asm.go @@ -148,15 +148,15 @@ func (ip *Interpreter) disassemble(slot Slot, slot2 Slot) string { case OpLe, OpBe: return fmt.Sprintf("%s%d r%d", mnemonic, slot.Uimm(), slot.Dst()) case OpJa: - return fmt.Sprintf("ja") + return "ja" case OpJeqImm, OpJgtImm, OpJgeImm, OpJltImm, OpJleImm, OpJsetImm, OpJneImm, OpJsgtImm, OpJsgeImm, OpJsltImm, OpJsleImm: return fmt.Sprintf("%s r%d, %d", mnemonic, slot.Dst(), int64(slot.Imm())) case OpJeqReg, OpJgtReg, OpJgeReg, OpJltReg, OpJleReg, OpJsetReg, OpJneReg, OpJsgtReg, OpJsgeReg, OpJsltReg, OpJsleReg: return fmt.Sprintf("%s r%d, r%d", mnemonic, slot.Dst(), slot.Src()) case OpCall: - return fmt.Sprintf("call") + return "call" case OpCallx: - return fmt.Sprintf("callx") + return "callx" case OpExit: return "exit" default: diff --git a/pkg/sbpf/errors.go b/pkg/sbpf/errors.go new file mode 100644 index 00000000..8dcbcc49 --- /dev/null +++ b/pkg/sbpf/errors.go @@ -0,0 +1,27 @@ +package sbpf + +import ( + "errors" + "fmt" +) + +// Exception codes. +var ( + ExcDivideByZero = errors.New("divide by zero at BPF instruction") + ExcDivideOverflow = errors.New("divide overflow") + ExcOutOfCU = errors.New("compute unit overrun") + ExcCallDepth = errors.New("call depth exceeded") + ExcInvalidInstr = errors.New("invalid instruction - feature not enabled") + ErrOutOfBounds = errors.New("value out of bounds") + ErrInvalidSectionHeader = errors.New("invalid section header") + ExcUnsupportedInstruction = errors.New("unsupported BPF instruction") +) + +type ErrStringTooLong struct { + Name string + Len uint64 +} + +func (e *ErrStringTooLong) Error() string { + return fmt.Sprintf("Section or symbol name `%s` is longer than `%d` bytes", e.Name, e.Len) +} diff --git a/pkg/sbpf/loader/loader.go b/pkg/sbpf/loader/loader.go index 79140c15..6deda6c2 100644 --- a/pkg/sbpf/loader/loader.go +++ b/pkg/sbpf/loader/loader.go @@ -7,7 +7,6 @@ import ( "bytes" "debug/elf" "encoding/binary" - "errors" "fmt" "io" @@ -16,18 +15,6 @@ import ( "github.com/Overclock-Validator/mithril/pkg/sbpf/sbpfver" ) -var ErrOutOfBounds = errors.New("value out of bounds") -var ErrInvalidSectionHeader = errors.New("invalid section header") - -type ErrStringTooLong struct { - Name string - Len uint64 -} - -func (e *ErrStringTooLong) Error() string { - return fmt.Sprintf("Section or symbol name `%s` is longer than `%d` bytes", e.Name, e.Len) -} - // TODO Fuzz // TODO Differential fuzz against rbpf diff --git a/pkg/sbpf/loader/parse.go b/pkg/sbpf/loader/parse.go index 12e8cdf5..e7e2e24f 100644 --- a/pkg/sbpf/loader/parse.go +++ b/pkg/sbpf/loader/parse.go @@ -11,6 +11,7 @@ import ( "math/bits" "strings" + "github.com/Overclock-Validator/mithril/pkg/sbpf" "github.com/Overclock-Validator/mithril/pkg/sbpf/sbpfver" ) @@ -91,7 +92,7 @@ func (l *Loader) newSymTableIter(sh *elf.Section64) (*symTableIter, error) { func (l *Loader) readHeader() error { if l.fileSize < ehLen { - return ErrOutOfBounds + return sbpf.ErrOutOfBounds } var hdrBuf [ehLen]byte @@ -297,22 +298,22 @@ func (l *Loader) readSectionHeaderTable() error { // See https://github.com/anza-xyz/sbpf/blob/main/src/elf_parser/mod.rs#L468 func (l *Loader) getString(strtab *elf.Section64, stroff uint32, maxLen uint16) (string, error) { if elf.SectionType(strtab.Type) != elf.SHT_STRTAB { - return "", ErrInvalidSectionHeader + return "", sbpf.ErrInvalidSectionHeader } offset, carry := bits.Add64(strtab.Off, uint64(stroff), 0) if carry != 0 { - return "", ErrOutOfBounds + return "", sbpf.ErrOutOfBounds } sectionEnd, carry := bits.Add64(strtab.Off, strtab.Size, 0) if carry != 0 { - return "", ErrOutOfBounds + return "", sbpf.ErrOutOfBounds } maxEnd, carry := bits.Add64(offset, uint64(maxLen), 0) if carry != 0 { - return "", ErrOutOfBounds + return "", sbpf.ErrOutOfBounds } if sectionEnd < maxEnd { @@ -320,7 +321,7 @@ func (l *Loader) getString(strtab *elf.Section64, stroff uint32, maxLen uint16) } if offset > l.fileSize { - return "", ErrOutOfBounds + return "", sbpf.ErrOutOfBounds } readLen := maxEnd - offset @@ -328,7 +329,7 @@ func (l *Loader) getString(strtab *elf.Section64, stroff uint32, maxLen uint16) readLen = l.fileSize - offset } if readLen == 0 { - return "", ErrOutOfBounds + return "", sbpf.ErrOutOfBounds } rd := bufio.NewReader(io.NewSectionReader(l.rd, int64(offset), int64(readLen))) @@ -338,7 +339,7 @@ func (l *Loader) getString(strtab *elf.Section64, stroff uint32, maxLen uint16) if err != nil { switch { case errors.Is(err, io.EOF): - return "", &ErrStringTooLong{Name: builder.String(), Len: readLen} + return "", &sbpf.ErrStringTooLong{Name: builder.String(), Len: readLen} default: return "", err } diff --git a/pkg/sbpf/loader/parse_test.go b/pkg/sbpf/loader/parse_test.go index 9694bea5..74d1d7b2 100644 --- a/pkg/sbpf/loader/parse_test.go +++ b/pkg/sbpf/loader/parse_test.go @@ -3,6 +3,8 @@ package loader import ( "debug/elf" "testing" + + "github.com/Overclock-Validator/mithril/pkg/sbpf" ) func TestLoader_getString(t *testing.T) { @@ -28,7 +30,7 @@ func TestLoader_getString(t *testing.T) { stroff: 0, maxLen: 16, want: "", - wantErr: ErrInvalidSectionHeader, + wantErr: sbpf.ErrInvalidSectionHeader, }, "out of bounds": { buf: []byte(".text\x00"), @@ -36,7 +38,7 @@ func TestLoader_getString(t *testing.T) { stroff: 0, maxLen: 16, want: "", - wantErr: ErrOutOfBounds, + wantErr: sbpf.ErrOutOfBounds, }, "section header too long": { buf: []byte(".data.rel.ro\x00"), @@ -44,7 +46,7 @@ func TestLoader_getString(t *testing.T) { stroff: 0, maxLen: 16, want: "", - wantErr: &ErrStringTooLong{Name: ".data.", Len: 6}, + wantErr: &sbpf.ErrStringTooLong{Name: ".data.", Len: 6}, }, } for name, tt := range tests { diff --git a/pkg/sbpf/vm.go b/pkg/sbpf/vm.go index 0c78bac6..2d5e3235 100644 --- a/pkg/sbpf/vm.go +++ b/pkg/sbpf/vm.go @@ -1,7 +1,6 @@ package sbpf import ( - "errors" "fmt" "github.com/Overclock-Validator/mithril/pkg/cu" @@ -68,17 +67,6 @@ func (e *Exception) Unwrap() error { return e.Detail } -// Exception codes. -var ( - ExcDivideByZero = errors.New("divide by zero at BPF instruction") - ExcDivideOverflow = errors.New("divide overflow") - ExcOutOfCU = errors.New("compute unit overrun") - ExcCallDepth = errors.New("call depth exceeded") - ExcInvalidInstr = errors.New("invalid instruction - feature not enabled") - - ExcUnsupportedInstruction = errors.New("unsupported BPF instruction") -) - type ExcBadAccess struct { Addr uint64 Size uint64