sbf: add ELF parser

Adds the first half of the ELF parser implementation,
based on Solana's new restricted SBFv2 ELF parser.
This commit is contained in:
Richard Patel 2022-09-01 18:04:25 +02:00
parent 4e5a36200f
commit 3230b09ec7
2 changed files with 209 additions and 0 deletions

207
pkg/sbf/elf.go Normal file
View File

@ -0,0 +1,207 @@
package sbf
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"io"
"math"
"math/bits"
)
// TODO Fuzz
// TODO Differential fuzz against rbpf
type Executable struct {
Header elf.Header64
Load elf.Prog64
ShStr elf.Section64
}
func LoadProgram(buf []byte) (*Executable, error) {
l := loader{
rd: bytes.NewReader(buf),
fileSize: uint64(len(buf)),
}
return l.load()
}
type loader struct {
rd io.ReaderAt
fileSize uint64
elf *Executable
}
func (l *loader) load() (*Executable, error) {
l.elf = new(Executable)
if err := l.readHeader(); err != nil {
return nil, err
}
if err := l.validateHeader(); err != nil {
return nil, err
}
if err := l.loadProgramHeaderTable(); err != nil {
return nil, err
}
if err := l.validateSectionHeaderTable(); err != nil {
return nil, err
}
// TODO load section name section header
// TODO parse sections
// TODO parse dynamic segment
return l.elf, nil
}
const (
ehsize = 0x40
phentsize = 0x38
shentsize = 0x40
)
func (l *loader) readHeader() error {
var hdrBuf [ehsize]byte
if _, err := io.ReadFull(io.NewSectionReader(l.rd, 0, ehsize), hdrBuf[:]); err != nil {
return err
}
return binary.Read(bytes.NewReader(hdrBuf[:]), binary.LittleEndian, &l.elf.Header)
}
func (l *loader) validateHeader() error {
eh := &l.elf.Header
ident := &eh.Ident
if string(ident[:elf.EI_CLASS]) != elf.ELFMAG {
return fmt.Errorf("not an ELF file")
}
if elf.Class(ident[elf.EI_CLASS]) != elf.ELFCLASS64 ||
elf.Data(ident[elf.EI_DATA]) != elf.ELFDATA2LSB ||
elf.Version(ident[elf.EI_VERSION]) != elf.EV_CURRENT ||
elf.OSABI(ident[elf.EI_OSABI]) != elf.ELFOSABI_NONE {
return fmt.Errorf("incompatible binary")
}
// note: EI_PAD and EI_ABIVERSION are ignored
if elf.Machine(eh.Machine) != elf.EM_BPF ||
elf.Type(eh.Type) != elf.ET_DYN ||
eh.Version != uint32(elf.EV_CURRENT) ||
eh.Ehsize != ehsize ||
eh.Phentsize != phentsize ||
eh.Shentsize != shentsize ||
eh.Shstrndx >= eh.Shnum {
return fmt.Errorf("invalid ELF file")
}
if eh.Phoff < ehsize {
return fmt.Errorf("program header overlaps with file header")
}
if eh.Shoff < ehsize {
return fmt.Errorf("section header overlaps with file header")
}
if isOverlap(eh.Phoff, uint64(eh.Phnum)*phentsize, eh.Shoff, uint64(eh.Shnum)*shentsize) {
return fmt.Errorf("program and section header overlap")
}
return nil
}
// scan the program header table and remember the last PT_LOAD segment
func (l *loader) loadProgramHeaderTable() error {
eh := &l.elf.Header
phoff := eh.Phoff
for i := uint16(0); i < eh.Phnum; i++ {
if phoff+phentsize > math.MaxInt64 {
return io.ErrUnexpectedEOF
}
rd := io.NewSectionReader(l.rd, int64(phoff), phentsize)
var ph elf.Prog64
if err := binary.Read(rd, binary.LittleEndian, &ph); err != nil {
return err
}
phoff += phentsize
if elf.ProgType(ph.Type) != elf.PT_LOAD {
continue
}
// vaddr must be ascending
if ph.Vaddr < l.elf.Load.Vaddr {
return fmt.Errorf("invalid program header")
}
segmentEnd, overflow := bits.Add64(ph.Off, ph.Filesz, 0)
if segmentEnd > l.fileSize || overflow > 0 {
return fmt.Errorf("segment out of bounds")
}
l.elf.Load = ph
}
return nil
}
func (l *loader) validateSectionHeaderTable() error {
eh := &l.elf.Header
shoff := eh.Shoff
offset := uint64(0)
for i := uint16(0); i < eh.Shnum; i++ {
if shoff+shentsize > math.MaxInt64 {
return io.ErrUnexpectedEOF
}
rd := io.NewSectionReader(l.rd, int64(shoff), shentsize)
var sh elf.Section64
if err := binary.Read(rd, binary.LittleEndian, &sh); err != nil {
return err
}
shoff += shentsize
if i == 0 {
if elf.SectionType(sh.Type) != elf.SHT_NULL {
return fmt.Errorf("section 0 is not SHT_NULL")
}
continue
}
if elf.SectionType(sh.Type) == elf.SHT_NOBITS {
continue
}
shend, overflow := bits.Add64(sh.Off, sh.Size, 0)
if overflow != 0 {
return fmt.Errorf("integer overflow in section %d", i)
}
if sh.Off < ehsize {
return fmt.Errorf("section %d overlaps with file header", i)
}
if isOverlap(eh.Phoff, uint64(eh.Phnum)*phentsize, sh.Off, sh.Size) {
return fmt.Errorf("section %d overlaps with program header", i)
}
if isOverlap(eh.Shoff, uint64(eh.Shnum)*shentsize, sh.Off, sh.Size) {
return fmt.Errorf("section %d overlaps with section header", i)
}
if eh.Shoff < offset {
return fmt.Errorf("sections not in order")
}
if shend > l.fileSize {
return fmt.Errorf("section %d out of bounds", i)
}
offset = shend
}
return nil
}
func isOverlap(startA uint64, sizeA uint64, startB uint64, sizeB uint64) bool {
if startA > startB {
startA, sizeA, startB, sizeB = startB, sizeB, startA, sizeA
}
endA, endB := startA+sizeA, startB+sizeB
if endA < startA || endB < startB {
panic("isOverlap: integer overflow")
}
return sizeA != 0 && sizeB != 0 && (startA == startB || endA > endB)
}

2
pkg/sbf/sbf.go Normal file
View File

@ -0,0 +1,2 @@
// Package sbf implements the Solana Bytecode Format.
package sbf