mirror of
https://github.com/sliverarmory/reflektor
synced 2026-08-25 09:14:48 +00:00
641 lines
24 KiB
Go
641 lines
24 KiB
Go
package bofloader
|
|
|
|
import (
|
|
"bytes"
|
|
"debug/elf"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
armELFEABIMask = 0xff000000
|
|
armELFEABI5 = 0x05000000
|
|
armELFFloatSoft = 0x00000200
|
|
armELFFloatHard = 0x00000400
|
|
|
|
riscvELFRVC = 0x00000001
|
|
riscvELFFloatABIMask = 0x00000006
|
|
riscvELFFloatABIDouble = 0x00000004
|
|
riscvELFRVE = 0x00000008
|
|
riscvELFTSO = 0x00000010
|
|
riscvELFKnownFlags = riscvELFRVC | riscvELFFloatABIMask | riscvELFRVE | riscvELFTSO
|
|
|
|
ppc64ELFABI = 0x00000003
|
|
ppc64ELFABI2 = 0x00000002
|
|
ppc64LocalEntryMask = 0xe0
|
|
ppc64ReservedSTOther = 0x1c
|
|
)
|
|
|
|
func parseELF(data []byte) (object *objectFile, err error) {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
object = nil
|
|
err = fmt.Errorf("bofloader: malformed ELF object: %v", recovered)
|
|
}
|
|
}()
|
|
|
|
if err := preflightELFHeader(data); err != nil {
|
|
return nil, err
|
|
}
|
|
file, err := elf.NewFile(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bofloader: parse ELF object: %w", err)
|
|
}
|
|
defer file.Close()
|
|
if file.Type != elf.ET_REL {
|
|
return nil, fmt.Errorf("bofloader: expected an ELF relocatable object, got %s", file.Type)
|
|
}
|
|
if file.Data != elf.ELFDATA2LSB {
|
|
return nil, fmt.Errorf("bofloader: only little-endian ELF objects are supported")
|
|
}
|
|
|
|
arch := ""
|
|
switch {
|
|
case file.Machine == elf.EM_386 && file.Class == elf.ELFCLASS32:
|
|
arch = "386"
|
|
case file.Machine == elf.EM_ARM && file.Class == elf.ELFCLASS32:
|
|
flags := binary.LittleEndian.Uint32(data[36:40])
|
|
if flags&armELFEABIMask != armELFEABI5 || flags&(armELFFloatSoft|armELFFloatHard) != armELFFloatHard {
|
|
return nil, fmt.Errorf("bofloader: ELF/arm BOFs require EABI5 hard-float flags, got %#08x", flags)
|
|
}
|
|
arch = "arm"
|
|
case file.Machine == elf.EM_X86_64 && file.Class == elf.ELFCLASS64:
|
|
arch = "amd64"
|
|
case file.Machine == elf.EM_AARCH64 && file.Class == elf.ELFCLASS64:
|
|
arch = "arm64"
|
|
case file.Machine == elf.EM_RISCV && file.Class == elf.ELFCLASS64:
|
|
flags := binary.LittleEndian.Uint32(data[48:52])
|
|
if flags&riscvELFFloatABIMask != riscvELFFloatABIDouble {
|
|
return nil, fmt.Errorf("bofloader: ELF/riscv64 BOFs require the LP64D double-float ABI, got flags %#08x", flags)
|
|
}
|
|
if flags&riscvELFRVE != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF/riscv64 BOFs cannot use the RV32E register ABI, got flags %#08x", flags)
|
|
}
|
|
if flags&riscvELFTSO != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF/riscv64 BOFs requiring RVTSO are unsupported, got flags %#08x", flags)
|
|
}
|
|
if unknown := flags &^ riscvELFKnownFlags; unknown != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF/riscv64 BOF has unknown flags %#08x", unknown)
|
|
}
|
|
arch = "riscv64"
|
|
case file.Machine == elf.EM_PPC64 && file.Class == elf.ELFCLASS64:
|
|
flags := binary.LittleEndian.Uint32(data[48:52])
|
|
if flags&ppc64ELFABI != ppc64ELFABI2 || flags&^uint32(ppc64ELFABI) != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF/ppc64le BOFs require the ELFv2 ABI flags, got %#08x", flags)
|
|
}
|
|
arch = "ppc64le"
|
|
default:
|
|
return nil, fmt.Errorf("bofloader: unsupported ELF machine/class %s/%s", file.Machine, file.Class)
|
|
}
|
|
mappedSectionBytes, err := preflightELFSections(file, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &objectFile{
|
|
format: "elf",
|
|
arch: arch,
|
|
symbols: make(map[uint32]objectSymbol),
|
|
}
|
|
sectionMap := make([]int, len(file.Sections))
|
|
for index := range sectionMap {
|
|
sectionMap[index] = sectionDiscarded
|
|
}
|
|
for sourceIndex, section := range file.Sections {
|
|
if section.Flags&elf.SHF_ALLOC == 0 {
|
|
continue
|
|
}
|
|
if section.Flags&elf.SHF_TLS != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF TLS section %q is unsupported", section.Name)
|
|
}
|
|
alignment := section.Addralign
|
|
if alignment == 0 {
|
|
alignment = 1
|
|
}
|
|
if alignment&(alignment-1) != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF section %q has invalid alignment %d", section.Name, alignment)
|
|
}
|
|
if alignment > maxImageSize || section.Size > maxImageSize {
|
|
return nil, fmt.Errorf("bofloader: ELF section %q exceeds loader limits", section.Name)
|
|
}
|
|
var contents []byte
|
|
if section.Type != elf.SHT_NOBITS && section.Size != 0 {
|
|
contents, err = section.Data()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bofloader: read ELF section %q: %w", section.Name, err)
|
|
}
|
|
if uint64(len(contents)) > section.Size {
|
|
return nil, fmt.Errorf("bofloader: ELF section %q data exceeds declared size", section.Name)
|
|
}
|
|
}
|
|
protection := protRead
|
|
if section.Flags&elf.SHF_WRITE != 0 {
|
|
protection |= protWrite
|
|
}
|
|
if section.Flags&elf.SHF_EXECINSTR != 0 {
|
|
protection |= protExec
|
|
}
|
|
sectionMap[sourceIndex] = len(result.sections)
|
|
result.sections = append(result.sections, objectSection{
|
|
name: section.Name,
|
|
data: contents,
|
|
size: section.Size,
|
|
align: alignment,
|
|
protection: protection,
|
|
mapped: section.Size != 0,
|
|
})
|
|
}
|
|
|
|
symbols, err := file.Symbols()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bofloader: read ELF symbols: %w", err)
|
|
}
|
|
commonOffset := uint64(0)
|
|
commonAlignment := uint64(1)
|
|
commonSymbols := make([]uint32, 0)
|
|
for position, raw := range symbols {
|
|
index := uint32(position + 1) // debug/elf omits ELF symbol table entry zero.
|
|
var section int
|
|
value := raw.Value
|
|
switch raw.Section {
|
|
case elf.SHN_UNDEF:
|
|
section = sectionUndefined
|
|
case elf.SHN_ABS:
|
|
section = sectionAbsolute
|
|
case elf.SHN_COMMON:
|
|
alignment := raw.Value
|
|
if alignment == 0 {
|
|
alignment = 1
|
|
}
|
|
if alignment&(alignment-1) != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF common symbol %q has invalid alignment %d", raw.Name, alignment)
|
|
}
|
|
if alignment > maxImageSize {
|
|
return nil, fmt.Errorf("bofloader: ELF common symbol %q alignment exceeds loader limits", raw.Name)
|
|
}
|
|
if alignment > commonAlignment {
|
|
commonAlignment = alignment
|
|
}
|
|
commonOffset = alignUp(commonOffset, alignment)
|
|
value = commonOffset
|
|
if raw.Size > maxImageSize-commonOffset {
|
|
return nil, fmt.Errorf("bofloader: ELF common symbols exceed %d bytes", maxImageSize)
|
|
}
|
|
commonOffset += raw.Size
|
|
section = sectionCommon
|
|
commonSymbols = append(commonSymbols, index)
|
|
default:
|
|
rawSection := int(raw.Section)
|
|
if rawSection < 0 || rawSection >= len(sectionMap) {
|
|
return nil, fmt.Errorf("bofloader: ELF symbol %q references invalid section %d", raw.Name, rawSection)
|
|
}
|
|
section = sectionMap[rawSection]
|
|
}
|
|
localEntry := uint64(0)
|
|
if arch == "ppc64le" {
|
|
if raw.Other&ppc64ReservedSTOther != 0 {
|
|
return nil, fmt.Errorf("bofloader: ELF/ppc64le symbol %q has reserved st_other bits %#02x", raw.Name, raw.Other&ppc64ReservedSTOther)
|
|
}
|
|
encoding := (raw.Other & ppc64LocalEntryMask) >> 5
|
|
switch encoding {
|
|
case 1:
|
|
return nil, fmt.Errorf("bofloader: ELF/ppc64le symbol %q uses unsupported local-entry encoding 1 (NOTOC caller-save-r2 semantics)", raw.Name)
|
|
case 7:
|
|
return nil, fmt.Errorf("bofloader: ELF/ppc64le symbol %q uses reserved local-entry encoding 7", raw.Name)
|
|
}
|
|
localEntry = ppc64LocalEntryOffset(raw.Other)
|
|
if localEntry != 0 {
|
|
if raw.Section == elf.SHN_UNDEF || elf.ST_TYPE(raw.Info) != elf.STT_FUNC {
|
|
return nil, fmt.Errorf("bofloader: ELF/ppc64le symbol %q has local-entry metadata but is not a defined function", raw.Name)
|
|
}
|
|
if raw.Size != 0 && localEntry >= raw.Size {
|
|
return nil, fmt.Errorf("bofloader: ELF/ppc64le symbol %q local entry offset %#x exceeds function size %#x", raw.Name, localEntry, raw.Size)
|
|
}
|
|
if section >= 0 {
|
|
sectionSize := result.sections[section].size
|
|
if value > sectionSize || localEntry > sectionSize-value {
|
|
return nil, fmt.Errorf("bofloader: ELF/ppc64le symbol %q local entry exceeds section %q", raw.Name, result.sections[section].name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
result.symbols[index] = objectSymbol{
|
|
index: index,
|
|
name: raw.Name,
|
|
section: section,
|
|
value: value,
|
|
size: raw.Size,
|
|
localEntry: localEntry,
|
|
weak: elf.ST_BIND(raw.Info) == elf.STB_WEAK,
|
|
}
|
|
}
|
|
if commonOffset != 0 {
|
|
if mappedSectionBytes > maxImageSize || commonOffset > maxImageSize-mappedSectionBytes {
|
|
return nil, fmt.Errorf("bofloader: cumulative ELF mapped section size exceeds %d bytes", maxImageSize)
|
|
}
|
|
commonSection := len(result.sections)
|
|
result.sections = append(result.sections, objectSection{
|
|
name: ".common",
|
|
size: commonOffset,
|
|
align: commonAlignment,
|
|
protection: protRead | protWrite,
|
|
mapped: true,
|
|
})
|
|
for _, index := range commonSymbols {
|
|
symbol := result.symbols[index]
|
|
symbol.section = commonSection
|
|
result.symbols[index] = symbol
|
|
}
|
|
}
|
|
|
|
symbolTableIndex := -1
|
|
for index, section := range file.Sections {
|
|
if section.Type == elf.SHT_SYMTAB {
|
|
symbolTableIndex = index
|
|
break
|
|
}
|
|
}
|
|
for _, section := range file.Sections {
|
|
if section.Type != elf.SHT_REL && section.Type != elf.SHT_RELA {
|
|
continue
|
|
}
|
|
if symbolTableIndex < 0 || int(section.Link) != symbolTableIndex {
|
|
return nil, fmt.Errorf("bofloader: ELF relocation section %q uses an unsupported symbol table", section.Name)
|
|
}
|
|
targetRaw := int(section.Info)
|
|
if targetRaw < 0 || targetRaw >= len(sectionMap) || sectionMap[targetRaw] < 0 {
|
|
continue
|
|
}
|
|
targetSection := sectionMap[targetRaw]
|
|
if !result.sections[targetSection].mapped {
|
|
continue
|
|
}
|
|
if (arch == "riscv64" || arch == "ppc64le") && section.Type != elf.SHT_RELA {
|
|
return nil, fmt.Errorf("bofloader: ELF/%s relocation section %q must use RELA encoding", arch, section.Name)
|
|
}
|
|
contents, dataErr := section.Data()
|
|
if dataErr != nil {
|
|
return nil, fmt.Errorf("bofloader: read ELF relocation section %q: %w", section.Name, dataErr)
|
|
}
|
|
relocations, decodeErr := decodeELFRelocations(file, section, contents, targetSection)
|
|
if decodeErr != nil {
|
|
return nil, fmt.Errorf("bofloader: decode ELF relocation section %q: %w", section.Name, decodeErr)
|
|
}
|
|
for _, relocation := range relocations {
|
|
if relocation.symbol != 0 {
|
|
if _, ok := result.symbols[relocation.symbol]; !ok {
|
|
return nil, fmt.Errorf("bofloader: ELF relocation references invalid symbol %d", relocation.symbol)
|
|
}
|
|
}
|
|
result.relocations = append(result.relocations, relocation)
|
|
}
|
|
}
|
|
if arch == "riscv64" {
|
|
if err := prepareELFRISCV64Pairs(result); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if arch == "ppc64le" {
|
|
if err := validateELFPPC64LERelocations(result); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func ppc64LocalEntryOffset(other byte) uint64 {
|
|
encoding := (other & ppc64LocalEntryMask) >> 5
|
|
return (uint64(1) << encoding) &^ uint64(3)
|
|
}
|
|
|
|
func preflightELFHeader(data []byte) error {
|
|
if len(data) < 16 {
|
|
return fmt.Errorf("bofloader: truncated ELF identification")
|
|
}
|
|
var headerSize, programCountField, sectionOffsetField, sectionEntrySizeField, sectionCountField, sectionNameIndexField int
|
|
var expectedSectionEntrySize uint64
|
|
switch elf.Class(data[elf.EI_CLASS]) {
|
|
case elf.ELFCLASS32:
|
|
headerSize = 52
|
|
programCountField = 44
|
|
sectionOffsetField = 32
|
|
sectionEntrySizeField = 46
|
|
sectionCountField = 48
|
|
sectionNameIndexField = 50
|
|
expectedSectionEntrySize = 40
|
|
case elf.ELFCLASS64:
|
|
headerSize = 64
|
|
programCountField = 56
|
|
sectionOffsetField = 40
|
|
sectionEntrySizeField = 58
|
|
sectionCountField = 60
|
|
sectionNameIndexField = 62
|
|
expectedSectionEntrySize = 64
|
|
default:
|
|
return nil // debug/elf reports the authoritative class error.
|
|
}
|
|
if len(data) < headerSize {
|
|
return fmt.Errorf("bofloader: truncated ELF header")
|
|
}
|
|
if elf.Data(data[elf.EI_DATA]) != elf.ELFDATA2LSB {
|
|
return fmt.Errorf("bofloader: only little-endian ELF objects are supported")
|
|
}
|
|
if elf.Type(binary.LittleEndian.Uint16(data[16:18])) != elf.ET_REL {
|
|
return fmt.Errorf("bofloader: expected an ELF relocatable object")
|
|
}
|
|
if programCount := binary.LittleEndian.Uint16(data[programCountField:]); programCount != 0 {
|
|
return fmt.Errorf("bofloader: ELF relocatable object has %d program headers", programCount)
|
|
}
|
|
sectionOffset := uint64(binary.LittleEndian.Uint32(data[sectionOffsetField:]))
|
|
if headerSize == 64 {
|
|
sectionOffset = binary.LittleEndian.Uint64(data[sectionOffsetField:])
|
|
}
|
|
sectionEntrySize := uint64(binary.LittleEndian.Uint16(data[sectionEntrySizeField:]))
|
|
sectionCount := uint64(binary.LittleEndian.Uint16(data[sectionCountField:]))
|
|
sectionNameIndex := uint64(binary.LittleEndian.Uint16(data[sectionNameIndexField:]))
|
|
if sectionCount == 0 && sectionOffset != 0 {
|
|
if sectionEntrySize < expectedSectionEntrySize || !parserRangeWithin(data, sectionOffset, expectedSectionEntrySize) {
|
|
return fmt.Errorf("bofloader: ELF extended section count is outside the object")
|
|
}
|
|
sectionSizeField := sectionOffset + 20
|
|
if headerSize == 64 {
|
|
sectionSizeField = sectionOffset + 32
|
|
sectionCount = binary.LittleEndian.Uint64(data[sectionSizeField:])
|
|
} else {
|
|
sectionCount = uint64(binary.LittleEndian.Uint32(data[sectionSizeField:]))
|
|
}
|
|
}
|
|
if sectionCount > maxObjectSections {
|
|
return fmt.Errorf("bofloader: ELF section count %d exceeds %d", sectionCount, maxObjectSections)
|
|
}
|
|
if sectionCount == 0 {
|
|
return nil
|
|
}
|
|
if sectionEntrySize < expectedSectionEntrySize {
|
|
return fmt.Errorf("bofloader: ELF section header size %d is smaller than %d", sectionEntrySize, expectedSectionEntrySize)
|
|
}
|
|
tableSize, ok := checkedMulUint64(sectionCount, sectionEntrySize)
|
|
if !ok || !parserRangeWithin(data, sectionOffset, tableSize) {
|
|
return fmt.Errorf("bofloader: ELF section header table is outside the object")
|
|
}
|
|
if sectionNameIndex == uint64(elf.SHN_XINDEX) {
|
|
sectionZero := sectionOffset
|
|
if headerSize == 64 {
|
|
sectionNameIndex = uint64(binary.LittleEndian.Uint32(data[sectionZero+40:]))
|
|
} else {
|
|
sectionNameIndex = uint64(binary.LittleEndian.Uint32(data[sectionZero+24:]))
|
|
}
|
|
}
|
|
if sectionNameIndex >= sectionCount {
|
|
return fmt.Errorf("bofloader: ELF section-name string table index %d exceeds section count %d", sectionNameIndex, sectionCount)
|
|
}
|
|
|
|
// debug/elf may decompress the section-name string table while constructing
|
|
// File, before parseELF can inspect the parsed sections. BOFs do not need
|
|
// compressed sections, so reject their raw headers before elf.NewFile can
|
|
// expand attacker-controlled compressed data.
|
|
for index := uint64(0); index < sectionCount; index++ {
|
|
headerOffset := sectionOffset + index*sectionEntrySize
|
|
_, sectionType, flags, rawOffset, rawSize, _ := rawELFSection(data, headerOffset, headerSize == 64)
|
|
if flags&uint64(elf.SHF_COMPRESSED) != 0 {
|
|
return fmt.Errorf("bofloader: compressed ELF sections are unsupported")
|
|
}
|
|
if elf.SectionType(sectionType) != elf.SHT_NOBITS && !parserRangeWithin(data, rawOffset, rawSize) {
|
|
return fmt.Errorf("bofloader: ELF section %d data is outside the object", index)
|
|
}
|
|
}
|
|
|
|
var sectionNames []byte
|
|
if sectionNameIndex != 0 {
|
|
nameHeader := sectionOffset + sectionNameIndex*sectionEntrySize
|
|
_, sectionType, _, rawOffset, rawSize, _ := rawELFSection(data, nameHeader, headerSize == 64)
|
|
if elf.SectionType(sectionType) != elf.SHT_STRTAB || !parserRangeWithin(data, rawOffset, rawSize) {
|
|
return fmt.Errorf("bofloader: ELF section-name string table is invalid")
|
|
}
|
|
if rawSize > maxObjectNameBytes {
|
|
return fmt.Errorf("bofloader: ELF section-name string table exceeds %d bytes", maxObjectNameBytes)
|
|
}
|
|
sectionNames = data[rawOffset : rawOffset+rawSize]
|
|
}
|
|
nameCache := make(map[uint64]uint64)
|
|
var resolvedNameBytes uint64
|
|
for index := uint64(0); index < sectionCount; index++ {
|
|
headerOffset := sectionOffset + index*sectionEntrySize
|
|
nameOffset, _, _, _, _, _ := rawELFSection(data, headerOffset, headerSize == 64)
|
|
if len(sectionNames) == 0 {
|
|
if nameOffset != 0 {
|
|
return fmt.Errorf("bofloader: ELF section %d has a name without a section-name string table", index)
|
|
}
|
|
continue
|
|
}
|
|
length, nameErr := parserStringLength(sectionNames, uint64(nameOffset), 0, nameCache)
|
|
if nameErr != nil {
|
|
return fmt.Errorf("bofloader: ELF section %d name: %w", index, nameErr)
|
|
}
|
|
resolvedNameBytes, ok = checkedAddUint64(resolvedNameBytes, length)
|
|
if !ok || resolvedNameBytes > maxObjectNameBytes {
|
|
return fmt.Errorf("bofloader: cumulative ELF section-name bytes exceed %d", maxObjectNameBytes)
|
|
}
|
|
name := sectionNames[uint64(nameOffset) : uint64(nameOffset)+length]
|
|
if bytes.HasPrefix(name, []byte(".zdebug")) {
|
|
return fmt.Errorf("bofloader: compressed ELF sections are unsupported")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func preflightELFSections(file *elf.File, data []byte) (uint64, error) {
|
|
if len(file.Sections) > maxObjectSections {
|
|
return 0, fmt.Errorf("bofloader: ELF section count %d exceeds %d", len(file.Sections), maxObjectSections)
|
|
}
|
|
var mappedBytes, symbolCount, relocationCount uint64
|
|
var symbolTable *elf.Section
|
|
for _, section := range file.Sections {
|
|
// debug/elf also recognizes the legacy .zdebug ZLIB convention lazily
|
|
// from section names. Reject it before any parser-owned Section.Data call
|
|
// can expand compressed metadata.
|
|
if strings.HasPrefix(section.Name, ".zdebug") {
|
|
return 0, fmt.Errorf("bofloader: compressed ELF sections are unsupported")
|
|
}
|
|
if section.Flags&elf.SHF_ALLOC != 0 {
|
|
var ok bool
|
|
mappedBytes, ok = checkedAddUint64(mappedBytes, section.Size)
|
|
if !ok || mappedBytes > maxImageSize {
|
|
return 0, fmt.Errorf("bofloader: cumulative ELF mapped section size exceeds %d bytes", maxImageSize)
|
|
}
|
|
}
|
|
|
|
var entrySize uint64
|
|
switch section.Type {
|
|
case elf.SHT_SYMTAB:
|
|
if symbolTable != nil {
|
|
return 0, fmt.Errorf("bofloader: multiple ELF symbol tables are unsupported")
|
|
}
|
|
symbolTable = section
|
|
if file.Class == elf.ELFCLASS64 {
|
|
entrySize = uint64(binary.Size(elf.Sym64{}))
|
|
} else {
|
|
entrySize = uint64(binary.Size(elf.Sym32{}))
|
|
}
|
|
if section.Size%entrySize != 0 {
|
|
return 0, fmt.Errorf("bofloader: ELF symbol section %q size %d is not a multiple of %d", section.Name, section.Size, entrySize)
|
|
}
|
|
count := section.Size / entrySize
|
|
var ok bool
|
|
symbolCount, ok = checkedAddUint64(symbolCount, count)
|
|
if !ok || symbolCount > maxObjectSymbols {
|
|
return 0, fmt.Errorf("bofloader: ELF symbol count exceeds %d", maxObjectSymbols)
|
|
}
|
|
case elf.SHT_REL, elf.SHT_RELA:
|
|
switch {
|
|
case file.Class == elf.ELFCLASS64 && section.Type == elf.SHT_RELA:
|
|
entrySize = uint64(binary.Size(elf.Rela64{}))
|
|
case file.Class == elf.ELFCLASS64:
|
|
entrySize = uint64(binary.Size(elf.Rel64{}))
|
|
case section.Type == elf.SHT_RELA:
|
|
entrySize = uint64(binary.Size(elf.Rela32{}))
|
|
default:
|
|
entrySize = uint64(binary.Size(elf.Rel32{}))
|
|
}
|
|
if section.Size%entrySize != 0 {
|
|
return 0, fmt.Errorf("bofloader: ELF relocation section %q size %d is not a multiple of %d", section.Name, section.Size, entrySize)
|
|
}
|
|
count := section.Size / entrySize
|
|
var ok bool
|
|
relocationCount, ok = checkedAddUint64(relocationCount, count)
|
|
if !ok || relocationCount > maxObjectRelocations {
|
|
return 0, fmt.Errorf("bofloader: ELF relocation count exceeds %d", maxObjectRelocations)
|
|
}
|
|
}
|
|
}
|
|
if symbolTable == nil {
|
|
return 0, fmt.Errorf("bofloader: ELF object has no symbol table")
|
|
}
|
|
expectedSymbolSize := uint64(binary.Size(elf.Sym32{}))
|
|
if file.Class == elf.ELFCLASS64 {
|
|
expectedSymbolSize = uint64(binary.Size(elf.Sym64{}))
|
|
}
|
|
if symbolTable.Entsize != expectedSymbolSize || symbolTable.Size%expectedSymbolSize != 0 {
|
|
return 0, fmt.Errorf("bofloader: ELF symbol table has invalid entry size")
|
|
}
|
|
if int(symbolTable.Link) <= 0 || int(symbolTable.Link) >= len(file.Sections) {
|
|
return 0, fmt.Errorf("bofloader: ELF symbol table has invalid string-table link %d", symbolTable.Link)
|
|
}
|
|
stringTable := file.Sections[symbolTable.Link]
|
|
if stringTable.Type != elf.SHT_STRTAB || stringTable.Size > maxObjectNameBytes ||
|
|
!parserRangeWithin(data, stringTable.Offset, stringTable.FileSize) ||
|
|
!parserRangeWithin(data, symbolTable.Offset, symbolTable.FileSize) {
|
|
return 0, fmt.Errorf("bofloader: ELF symbol or string table is outside parser limits")
|
|
}
|
|
symbolData := data[symbolTable.Offset : symbolTable.Offset+symbolTable.FileSize]
|
|
stringData := data[stringTable.Offset : stringTable.Offset+stringTable.FileSize]
|
|
nameCache := make(map[uint64]uint64)
|
|
var resolvedSymbolNameBytes uint64
|
|
for offset := uint64(0); offset < uint64(len(symbolData)); offset += expectedSymbolSize {
|
|
nameOffset := uint64(binary.LittleEndian.Uint32(symbolData[offset:]))
|
|
length, nameErr := parserStringLength(stringData, nameOffset, 0, nameCache)
|
|
if nameErr != nil {
|
|
return 0, fmt.Errorf("bofloader: ELF symbol name: %w", nameErr)
|
|
}
|
|
var ok bool
|
|
resolvedSymbolNameBytes, ok = checkedAddUint64(resolvedSymbolNameBytes, length)
|
|
if !ok || resolvedSymbolNameBytes > maxObjectNameBytes {
|
|
return 0, fmt.Errorf("bofloader: cumulative ELF symbol-name bytes exceed %d", maxObjectNameBytes)
|
|
}
|
|
}
|
|
return mappedBytes, nil
|
|
}
|
|
|
|
func rawELFSection(data []byte, offset uint64, is64 bool) (name uint32, sectionType uint32, flags, rawOffset, rawSize uint64, link uint32) {
|
|
name = binary.LittleEndian.Uint32(data[offset:])
|
|
sectionType = binary.LittleEndian.Uint32(data[offset+4:])
|
|
if is64 {
|
|
flags = binary.LittleEndian.Uint64(data[offset+8:])
|
|
rawOffset = binary.LittleEndian.Uint64(data[offset+24:])
|
|
rawSize = binary.LittleEndian.Uint64(data[offset+32:])
|
|
link = binary.LittleEndian.Uint32(data[offset+40:])
|
|
return
|
|
}
|
|
flags = uint64(binary.LittleEndian.Uint32(data[offset+8:]))
|
|
rawOffset = uint64(binary.LittleEndian.Uint32(data[offset+16:]))
|
|
rawSize = uint64(binary.LittleEndian.Uint32(data[offset+20:]))
|
|
link = binary.LittleEndian.Uint32(data[offset+24:])
|
|
return
|
|
}
|
|
|
|
func parserStringLength(table []byte, offset, minimum uint64, cache map[uint64]uint64) (uint64, error) {
|
|
if offset < minimum || offset >= uint64(len(table)) {
|
|
return 0, fmt.Errorf("string offset %d is outside a %d-byte table", offset, len(table))
|
|
}
|
|
if length, ok := cache[offset]; ok {
|
|
return length, nil
|
|
}
|
|
remaining := table[offset:]
|
|
scan := len(remaining)
|
|
if scan > maxObjectNameSize+1 {
|
|
scan = maxObjectNameSize + 1
|
|
}
|
|
terminator := bytes.IndexByte(remaining[:scan], 0)
|
|
if terminator < 0 {
|
|
if len(remaining) > maxObjectNameSize {
|
|
return 0, fmt.Errorf("name at offset %d exceeds %d bytes", offset, maxObjectNameSize)
|
|
}
|
|
return 0, fmt.Errorf("name at offset %d is not NUL-terminated", offset)
|
|
}
|
|
length := uint64(terminator)
|
|
cache[offset] = length
|
|
return length, nil
|
|
}
|
|
|
|
func decodeELFRelocations(file *elf.File, section *elf.Section, contents []byte, targetSection int) ([]objectRelocation, error) {
|
|
reader := bytes.NewReader(contents)
|
|
result := make([]objectRelocation, 0)
|
|
for reader.Len() != 0 {
|
|
var relocation objectRelocation
|
|
relocation.section = targetSection
|
|
switch {
|
|
case file.Class == elf.ELFCLASS64 && section.Type == elf.SHT_RELA:
|
|
var raw elf.Rela64
|
|
if err := binary.Read(reader, file.ByteOrder, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
relocation.offset = raw.Off
|
|
relocation.typeID = elf.R_TYPE64(raw.Info)
|
|
relocation.symbol = elf.R_SYM64(raw.Info)
|
|
relocation.addend = raw.Addend
|
|
relocation.hasAdd = true
|
|
case file.Class == elf.ELFCLASS64 && section.Type == elf.SHT_REL:
|
|
var raw elf.Rel64
|
|
if err := binary.Read(reader, file.ByteOrder, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
relocation.offset = raw.Off
|
|
relocation.typeID = elf.R_TYPE64(raw.Info)
|
|
relocation.symbol = elf.R_SYM64(raw.Info)
|
|
case file.Class == elf.ELFCLASS32 && section.Type == elf.SHT_RELA:
|
|
var raw elf.Rela32
|
|
if err := binary.Read(reader, file.ByteOrder, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
relocation.offset = uint64(raw.Off)
|
|
relocation.typeID = elf.R_TYPE32(raw.Info)
|
|
relocation.symbol = elf.R_SYM32(raw.Info)
|
|
relocation.addend = int64(raw.Addend)
|
|
relocation.hasAdd = true
|
|
case file.Class == elf.ELFCLASS32 && section.Type == elf.SHT_REL:
|
|
var raw elf.Rel32
|
|
if err := binary.Read(reader, file.ByteOrder, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
relocation.offset = uint64(raw.Off)
|
|
relocation.typeID = elf.R_TYPE32(raw.Info)
|
|
relocation.symbol = elf.R_SYM32(raw.Info)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported ELF relocation encoding")
|
|
}
|
|
result = append(result, relocation)
|
|
}
|
|
return result, nil
|
|
}
|