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 916ccb73..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,8 +15,6 @@ import ( "github.com/Overclock-Validator/mithril/pkg/sbpf/sbpfver" ) -var ErrOutOfBounds = errors.New("value out of bounds") - // TODO Fuzz // TODO Differential fuzz against rbpf diff --git a/pkg/sbpf/loader/parse.go b/pkg/sbpf/loader/parse.go index 4d1767d7..e7e2e24f 100644 --- a/pkg/sbpf/loader/parse.go +++ b/pkg/sbpf/loader/parse.go @@ -4,12 +4,14 @@ import ( "bufio" "debug/elf" "encoding/binary" + "errors" "fmt" "io" "math" "math/bits" "strings" + "github.com/Overclock-Validator/mithril/pkg/sbpf" "github.com/Overclock-Validator/mithril/pkg/sbpf/sbpfver" ) @@ -90,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 @@ -291,26 +293,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 "", sbpf.ErrInvalidSectionHeader + } + + offset, carry := bits.Add64(strtab.Off, uint64(stroff), 0) + if carry != 0 { + return "", sbpf.ErrOutOfBounds + } + + sectionEnd, carry := bits.Add64(strtab.Off, strtab.Size, 0) + if carry != 0 { + return "", sbpf.ErrOutOfBounds + } + + maxEnd, carry := bits.Add64(offset, uint64(maxLen), 0) + if carry != 0 { + return "", sbpf.ErrOutOfBounds + } + + if sectionEnd < maxEnd { + maxEnd = sectionEnd + } + + if offset > l.fileSize { + return "", sbpf.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 "", sbpf.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 "", &sbpf.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..74d1d7b2 --- /dev/null +++ b/pkg/sbpf/loader/parse_test.go @@ -0,0 +1,70 @@ +package loader + +import ( + "debug/elf" + "testing" + + "github.com/Overclock-Validator/mithril/pkg/sbpf" +) + +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: sbpf.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: sbpf.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: &sbpf.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) + } + }) + } +} 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