sbf: add ELF section parser
This commit is contained in:
parent
cc798726bc
commit
fade5aa0b8
239
pkg/sbf/elf.go
239
pkg/sbf/elf.go
|
@ -1,6 +1,7 @@
|
||||||
package sbf
|
package sbf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
@ -8,6 +9,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO Fuzz
|
// TODO Fuzz
|
||||||
|
@ -16,10 +18,26 @@ import (
|
||||||
type Executable struct {
|
type Executable struct {
|
||||||
Header elf.Header64
|
Header elf.Header64
|
||||||
Load elf.Prog64
|
Load elf.Prog64
|
||||||
ShStr elf.Section64
|
ShShstrtab elf.Section64
|
||||||
|
ShSymtab *elf.Section64
|
||||||
|
ShStrtab *elf.Section64
|
||||||
|
ShDynstr *elf.Section64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bounds checks
|
||||||
|
const (
|
||||||
|
// 64 MiB max program size.
|
||||||
|
// Allows loader to use unchecked math when adding 32-bit offsets.
|
||||||
|
maxFileLen = 1 << 26
|
||||||
|
|
||||||
|
maxSectionNameLen = 16
|
||||||
|
maxSymbolNameLen = 1024
|
||||||
|
)
|
||||||
|
|
||||||
func LoadProgram(buf []byte) (*Executable, error) {
|
func LoadProgram(buf []byte) (*Executable, error) {
|
||||||
|
if len(buf) > maxFileLen {
|
||||||
|
return nil, fmt.Errorf("ELF file too large")
|
||||||
|
}
|
||||||
l := loader{
|
l := loader{
|
||||||
rd: bytes.NewReader(buf),
|
rd: bytes.NewReader(buf),
|
||||||
fileSize: uint64(len(buf)),
|
fileSize: uint64(len(buf)),
|
||||||
|
@ -27,6 +45,8 @@ func LoadProgram(buf []byte) (*Executable, error) {
|
||||||
return l.load()
|
return l.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EF_SBF_V2 = 0x20
|
||||||
|
|
||||||
type loader struct {
|
type loader struct {
|
||||||
rd io.ReaderAt
|
rd io.ReaderAt
|
||||||
fileSize uint64
|
fileSize uint64
|
||||||
|
@ -44,11 +64,12 @@ func (l *loader) load() (*Executable, error) {
|
||||||
if err := l.loadProgramHeaderTable(); err != nil {
|
if err := l.loadProgramHeaderTable(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := l.validateSectionHeaderTable(); err != nil {
|
if err := l.readSectionHeaderTable(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := l.parseSections(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO load section name section header
|
|
||||||
// TODO parse sections
|
|
||||||
// TODO parse dynamic segment
|
// TODO parse dynamic segment
|
||||||
return l.elf, nil
|
return l.elf, nil
|
||||||
}
|
}
|
||||||
|
@ -59,6 +80,16 @@ const (
|
||||||
shentsize = 0x40
|
shentsize = 0x40
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (l *loader) newPhTableIter() *tableIter[elf.Prog64] {
|
||||||
|
eh := &l.elf.Header
|
||||||
|
return newTableIterator[elf.Prog64](l, eh.Phoff, eh.Phnum, phentsize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loader) newShTableIter() *tableIter[elf.Section64] {
|
||||||
|
eh := &l.elf.Header
|
||||||
|
return newTableIterator[elf.Section64](l, eh.Shoff, eh.Shnum, shentsize)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *loader) readHeader() error {
|
func (l *loader) readHeader() error {
|
||||||
var hdrBuf [ehsize]byte
|
var hdrBuf [ehsize]byte
|
||||||
if _, err := io.ReadFull(io.NewSectionReader(l.rd, 0, ehsize), hdrBuf[:]); err != nil {
|
if _, err := io.ReadFull(io.NewSectionReader(l.rd, 0, ehsize), hdrBuf[:]); err != nil {
|
||||||
|
@ -78,14 +109,14 @@ func (l *loader) validateHeader() error {
|
||||||
if elf.Class(ident[elf.EI_CLASS]) != elf.ELFCLASS64 ||
|
if elf.Class(ident[elf.EI_CLASS]) != elf.ELFCLASS64 ||
|
||||||
elf.Data(ident[elf.EI_DATA]) != elf.ELFDATA2LSB ||
|
elf.Data(ident[elf.EI_DATA]) != elf.ELFDATA2LSB ||
|
||||||
elf.Version(ident[elf.EI_VERSION]) != elf.EV_CURRENT ||
|
elf.Version(ident[elf.EI_VERSION]) != elf.EV_CURRENT ||
|
||||||
elf.OSABI(ident[elf.EI_OSABI]) != elf.ELFOSABI_NONE {
|
elf.OSABI(ident[elf.EI_OSABI]) != elf.ELFOSABI_NONE ||
|
||||||
|
elf.Machine(eh.Machine) != elf.EM_BPF ||
|
||||||
|
elf.Type(eh.Type) != elf.ET_DYN {
|
||||||
return fmt.Errorf("incompatible binary")
|
return fmt.Errorf("incompatible binary")
|
||||||
}
|
}
|
||||||
// note: EI_PAD and EI_ABIVERSION are ignored
|
// note: EI_PAD and EI_ABIVERSION are ignored
|
||||||
|
|
||||||
if elf.Machine(eh.Machine) != elf.EM_BPF ||
|
if eh.Version != uint32(elf.EV_CURRENT) ||
|
||||||
elf.Type(eh.Type) != elf.ET_DYN ||
|
|
||||||
eh.Version != uint32(elf.EV_CURRENT) ||
|
|
||||||
eh.Ehsize != ehsize ||
|
eh.Ehsize != ehsize ||
|
||||||
eh.Phentsize != phentsize ||
|
eh.Phentsize != phentsize ||
|
||||||
eh.Shentsize != shentsize ||
|
eh.Shentsize != shentsize ||
|
||||||
|
@ -108,20 +139,9 @@ func (l *loader) validateHeader() error {
|
||||||
|
|
||||||
// scan the program header table and remember the last PT_LOAD segment
|
// scan the program header table and remember the last PT_LOAD segment
|
||||||
func (l *loader) loadProgramHeaderTable() error {
|
func (l *loader) loadProgramHeaderTable() error {
|
||||||
eh := &l.elf.Header
|
iter := l.newPhTableIter()
|
||||||
phoff := eh.Phoff
|
for iter.Next() && iter.Err() == nil {
|
||||||
|
ph := iter.Item()
|
||||||
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 {
|
if elf.ProgType(ph.Type) != elf.PT_LOAD {
|
||||||
continue
|
continue
|
||||||
|
@ -139,37 +159,30 @@ func (l *loader) loadProgramHeaderTable() error {
|
||||||
|
|
||||||
l.elf.Load = ph
|
l.elf.Load = ph
|
||||||
}
|
}
|
||||||
|
return iter.Err()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loader) validateSectionHeaderTable() error {
|
// reads and validates the section header table.
|
||||||
|
// remembers the section header table.
|
||||||
|
func (l *loader) readSectionHeaderTable() error {
|
||||||
eh := &l.elf.Header
|
eh := &l.elf.Header
|
||||||
shoff := eh.Shoff
|
iter := l.newShTableIter()
|
||||||
|
sectionDataOff := uint64(0)
|
||||||
|
|
||||||
offset := uint64(0)
|
if !iter.Next() {
|
||||||
for i := uint16(0); i < eh.Shnum; i++ {
|
return fmt.Errorf("missing section 0")
|
||||||
if shoff+shentsize > math.MaxInt64 {
|
|
||||||
return io.ErrUnexpectedEOF
|
|
||||||
}
|
}
|
||||||
rd := io.NewSectionReader(l.rd, int64(shoff), shentsize)
|
if elf.SectionType(iter.Item().Type) != elf.SHT_NULL {
|
||||||
|
|
||||||
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")
|
return fmt.Errorf("section 0 is not SHT_NULL")
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
for iter.Next() && iter.Err() == nil {
|
||||||
|
i, sh := iter.Index(), iter.Item()
|
||||||
if elf.SectionType(sh.Type) == elf.SHT_NOBITS {
|
if elf.SectionType(sh.Type) == elf.SHT_NOBITS {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure section data is not overlapping with ELF headers
|
||||||
shend, overflow := bits.Add64(sh.Off, sh.Size, 0)
|
shend, overflow := bits.Add64(sh.Off, sh.Size, 0)
|
||||||
if overflow != 0 {
|
if overflow != 0 {
|
||||||
return fmt.Errorf("integer overflow in section %d", i)
|
return fmt.Errorf("integer overflow in section %d", i)
|
||||||
|
@ -183,17 +196,155 @@ func (l *loader) validateSectionHeaderTable() error {
|
||||||
if isOverlap(eh.Shoff, uint64(eh.Shnum)*shentsize, sh.Off, sh.Size) {
|
if isOverlap(eh.Shoff, uint64(eh.Shnum)*shentsize, sh.Off, sh.Size) {
|
||||||
return fmt.Errorf("section %d overlaps with section header", i)
|
return fmt.Errorf("section %d overlaps with section header", i)
|
||||||
}
|
}
|
||||||
if eh.Shoff < offset {
|
|
||||||
|
// More checks
|
||||||
|
if eh.Shoff < sectionDataOff {
|
||||||
return fmt.Errorf("sections not in order")
|
return fmt.Errorf("sections not in order")
|
||||||
}
|
}
|
||||||
if shend > l.fileSize {
|
if shend > l.fileSize {
|
||||||
return fmt.Errorf("section %d out of bounds", i)
|
return fmt.Errorf("section %d out of bounds", i)
|
||||||
}
|
}
|
||||||
offset = shend
|
|
||||||
|
// Remember section header string table.
|
||||||
|
if eh.Shstrndx != uint16(elf.SHN_UNDEF) && eh.Shstrndx == i {
|
||||||
|
l.elf.ShShstrtab = sh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sectionDataOff = shend
|
||||||
|
}
|
||||||
|
// TODO validate offset and size (?)
|
||||||
|
if elf.SectionType(l.elf.ShShstrtab.Type) != elf.SHT_STRTAB {
|
||||||
|
return fmt.Errorf("invalid .shstrtab")
|
||||||
|
}
|
||||||
|
return iter.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
offset := strtab.Off + uint64(stroff)
|
||||||
|
if offset > l.fileSize || offset+uint64(maxLen) > l.fileSize {
|
||||||
|
return "", io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
rd := bufio.NewReader(io.NewSectionReader(l.rd, int64(offset), int64(maxLen)))
|
||||||
|
var builder strings.Builder
|
||||||
|
for {
|
||||||
|
b, err := rd.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if b == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
builder.WriteByte(b)
|
||||||
|
}
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate sections and remember special sections by name.
|
||||||
|
func (l *loader) parseSections() error {
|
||||||
|
shShstrtab := &l.elf.ShShstrtab
|
||||||
|
iter := l.newShTableIter()
|
||||||
|
for iter.Next() && iter.Err() == nil {
|
||||||
|
sh := iter.Item()
|
||||||
|
sectionName, err := l.getString(shShstrtab, sh.Name, maxSectionNameLen)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getString: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember special section or error if it already exists.
|
||||||
|
setSection := func(shPtr **elf.Section64) error {
|
||||||
|
if *shPtr != nil {
|
||||||
|
return fmt.Errorf("duplicate section: %s", sectionName)
|
||||||
|
}
|
||||||
|
*shPtr = new(elf.Section64)
|
||||||
|
**shPtr = sh
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
switch sectionName {
|
||||||
|
case ".symtab":
|
||||||
|
err = setSection(&l.elf.ShSymtab)
|
||||||
|
case ".strtab":
|
||||||
|
err = setSection(&l.elf.ShStrtab)
|
||||||
|
case ".dynstr":
|
||||||
|
err = setSection(&l.elf.ShDynstr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iter.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// tableIter is a memory-efficient iterator over densely packed tables of statically sized items.
|
||||||
|
// Such as the ELF program header and section header tables.
|
||||||
|
type tableIter[T any] struct {
|
||||||
|
l *loader
|
||||||
|
off uint64
|
||||||
|
i uint16 // one ahead
|
||||||
|
count uint16
|
||||||
|
elemSize uint16
|
||||||
|
elem T
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTableIterator creates a new tableIter at `off` for `count` elements of `elemSize` len.
|
||||||
|
func newTableIterator[T any](l *loader, off uint64, count uint16, elemSize uint16) *tableIter[T] {
|
||||||
|
return &tableIter[T]{
|
||||||
|
l: l,
|
||||||
|
off: off,
|
||||||
|
count: count,
|
||||||
|
elemSize: elemSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next reads one element.
|
||||||
|
//
|
||||||
|
// Returns true on success, false if table end has been reached or error occurred.
|
||||||
|
// The caller should abort iteration on error.
|
||||||
|
func (it *tableIter[T]) Next() (ok bool) {
|
||||||
|
ok, it.err = it.getNext()
|
||||||
|
if ok && it.err != nil {
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the current table index.
|
||||||
|
func (it *tableIter[T]) Index() uint16 {
|
||||||
|
return it.i - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the current error.
|
||||||
|
func (it *tableIter[T]) Err() error {
|
||||||
|
return it.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item returns the current element read.
|
||||||
|
//
|
||||||
|
// Next must be called before.
|
||||||
|
func (it *tableIter[T]) Item() T {
|
||||||
|
return it.elem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *tableIter[T]) getNext() (bool, error) {
|
||||||
|
if it.i >= it.count {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if it.off >= math.MaxInt64 || it.off+uint64(it.elemSize) > math.MaxInt64 {
|
||||||
|
return false, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
rd := io.NewSectionReader(it.l.rd, int64(it.off), int64(it.elemSize))
|
||||||
|
if err := binary.Read(rd, binary.LittleEndian, &it.elem); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
it.off += uint64(it.elemSize)
|
||||||
|
it.i++
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isOverlap(startA uint64, sizeA uint64, startB uint64, sizeB uint64) bool {
|
func isOverlap(startA uint64, sizeA uint64, startB uint64, sizeB uint64) bool {
|
||||||
if startA > startB {
|
if startA > startB {
|
||||||
|
|
|
@ -48,6 +48,50 @@ func TestLoadProgram_Noop(t *testing.T) {
|
||||||
Memsz: 208,
|
Memsz: 208,
|
||||||
Align: 4096,
|
Align: 4096,
|
||||||
},
|
},
|
||||||
ShStr: elf.Section64{},
|
ShShstrtab: elf.Section64{
|
||||||
|
Name: 82,
|
||||||
|
Type: uint32(elf.SHT_STRTAB),
|
||||||
|
Flags: 0,
|
||||||
|
Addr: 0,
|
||||||
|
Off: 8648,
|
||||||
|
Size: 100,
|
||||||
|
Addralign: 1,
|
||||||
|
},
|
||||||
|
ShSymtab: &elf.Section64{
|
||||||
|
Name: 74,
|
||||||
|
Type: uint32(elf.SHT_SYMTAB),
|
||||||
|
Flags: 0,
|
||||||
|
Addr: 0,
|
||||||
|
Off: 8504,
|
||||||
|
Size: 144,
|
||||||
|
Link: 12,
|
||||||
|
Info: 3,
|
||||||
|
Addralign: 8,
|
||||||
|
Entsize: 24,
|
||||||
|
},
|
||||||
|
ShStrtab: &elf.Section64{
|
||||||
|
Name: 92,
|
||||||
|
Type: uint32(elf.SHT_STRTAB),
|
||||||
|
Flags: 0,
|
||||||
|
Addr: 0,
|
||||||
|
Off: 8748,
|
||||||
|
Size: 39,
|
||||||
|
Link: 0,
|
||||||
|
Info: 0,
|
||||||
|
Addralign: 1,
|
||||||
|
Entsize: 0,
|
||||||
|
},
|
||||||
|
ShDynstr: &elf.Section64{
|
||||||
|
Name: 25,
|
||||||
|
Type: uint32(elf.SHT_STRTAB),
|
||||||
|
Flags: uint64(elf.DF_SYMBOLIC),
|
||||||
|
Addr: 624,
|
||||||
|
Off: 624,
|
||||||
|
Size: 23,
|
||||||
|
Link: 0,
|
||||||
|
Info: 0,
|
||||||
|
Addralign: 1,
|
||||||
|
Entsize: 0,
|
||||||
|
},
|
||||||
}, exe)
|
}, exe)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue