Skip to content

Commit

Permalink
fix: symtab parsing when macho is silly #42
Browse files Browse the repository at this point in the history
  • Loading branch information
blacktop committed Apr 5, 2024
1 parent 5a17da7 commit 21cfc1d
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 47 deletions.
8 changes: 2 additions & 6 deletions cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"unsafe"

"github.com/blacktop/go-macho/internal/saferio"
"github.com/blacktop/go-macho/pkg/codesign"
"github.com/blacktop/go-macho/types"
)
Expand Down Expand Up @@ -205,12 +206,7 @@ func (s *Segment) Write(buf *bytes.Buffer, o binary.ByteOrder) error {

// Data reads and returns the contents of the segment.
func (s *Segment) Data() ([]byte, error) {
dat := make([]byte, s.Filesz)
n, err := s.ReadAt(dat, int64(s.Offset))
if n == len(dat) {
err = nil
}
return dat[0:n], err
return saferio.ReadDataAt(s.sr, s.Filesz, 0)
}

// Open returns a new ReadSeeker reading the segment.
Expand Down
92 changes: 58 additions & 34 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/blacktop/go-dwarf"

Check failure on line 19 in file.go

View workflow job for this annotation

GitHub Actions / Build

missing go.sum entry for module providing package github.com/blacktop/go-dwarf (imported by github.com/blacktop/go-macho); to add:

"github.com/blacktop/go-macho/internal/saferio"
"github.com/blacktop/go-macho/pkg/codesign"
"github.com/blacktop/go-macho/pkg/fixupchains"
"github.com/blacktop/go-macho/pkg/trie"
Expand Down Expand Up @@ -183,11 +184,15 @@ func NewFile(r io.ReaderAt, config ...FileConfig) (*File, error) {
if f.Magic == types.Magic64 {
offset = types.FileHeaderSize64
}
dat := make([]byte, f.SizeCommands)
if _, err := r.ReadAt(dat, offset); err != nil {
return nil, fmt.Errorf("failed to parse command dat: %v", err)
dat, err := saferio.ReadDataAt(r, uint64(f.SizeCommands), offset)
if err != nil {
return nil, err
}
c := saferio.SliceCap[Load](uint64(f.NCommands))
if c < 0 {
return nil, &FormatError{offset, "too many load commands", nil}
}
f.Loads = []Load{}
f.Loads = make([]Load, 0, c)
bo := f.ByteOrder
for i := uint32(0); i < f.NCommands; i++ {
// Each load command begins with uint32 command and length.
Expand Down Expand Up @@ -312,14 +317,20 @@ func NewFile(r io.ReaderAt, config ...FileConfig) (*File, error) {
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, fmt.Errorf("failed to read LC_SYMTAB: %v", err)
}
symdat := make([]byte, int(hdr.Nsyms)*f.symbolSize())
if _, err := f.cr.ReadAt(symdat, int64(hdr.Symoff)); err != nil {
return nil, fmt.Errorf("failed to read data at Symoff=%#x; %v", int64(hdr.Symoff), err)
}
strtab := make([]byte, hdr.Strsize)
if _, err := f.cr.ReadAt(strtab, int64(hdr.Stroff)); err != nil {
strtab, err := saferio.ReadDataAt(f.cr, uint64(hdr.Strsize), int64(hdr.Stroff))
if err != nil {
return nil, fmt.Errorf("failed to read data at Stroff=%#x; %v", int64(hdr.Stroff), err)
}
var symsz int
if f.Magic == types.Magic64 {
symsz = 16
} else {
symsz = 12
}
symdat, err := saferio.ReadDataAt(r, uint64(hdr.Nsyms)*uint64(symsz), int64(hdr.Symoff))
if err != nil {
return nil, fmt.Errorf("failed to read data at Symoff=%#x; %v", int64(hdr.Symoff), err)
}
st, err := f.parseSymtab(symdat, strtab, cmddat, &hdr, offset)
if err != nil {
return nil, fmt.Errorf("failed to read parseSymtab: %v", err)
Expand Down Expand Up @@ -494,18 +505,20 @@ func NewFile(r io.ReaderAt, config ...FileConfig) (*File, error) {
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, fmt.Errorf("failed to read LC_DYSYMTAB: %v", err)
}
if f.Symtab != nil && hdr.Iundefsym > uint32(len(f.Symtab.Syms)) {
if f.Symtab == nil {
return nil, &FormatError{offset, "dynamic symbol table seen before any ordinary symbol table", nil}
} else if hdr.Iundefsym > uint32(len(f.Symtab.Syms)) {
return nil, &FormatError{offset, fmt.Sprintf(
"undefined symbols index in dynamic symbol table command is greater than symbol table length (%d > %d)",
hdr.Iundefsym, len(f.Symtab.Syms)), nil}
} else if f.Symtab != nil && hdr.Iundefsym+hdr.Nundefsym > uint32(len(f.Symtab.Syms)) {
} else if hdr.Iundefsym+hdr.Nundefsym > uint32(len(f.Symtab.Syms)) {
return nil, &FormatError{offset, fmt.Sprintf(
"number of undefined symbols after index in dynamic symbol table command is greater than symbol table length (%d > %d)",
hdr.Iundefsym+hdr.Nundefsym, len(f.Symtab.Syms)), nil}
}
dat := make([]byte, hdr.Nindirectsyms*4)
if _, err := f.cr.ReadAt(dat, int64(hdr.Indirectsymoff)); err != nil {
return nil, fmt.Errorf("failed to read data at Indirectsymoff=%#x; %v", int64(hdr.Indirectsymoff), err)
dat, err := saferio.ReadDataAt(r, uint64(hdr.Nindirectsyms)*4, int64(hdr.Indirectsymoff))
if err != nil {
return nil, fmt.Errorf("failed to read data at Indirectsymoff @ %#x: %w", int64(hdr.Indirectsymoff), err)
}
x := make([]uint32, hdr.Nindirectsyms)
if err := binary.Read(bytes.NewReader(dat), bo, x); err != nil {
Expand Down Expand Up @@ -1231,6 +1244,12 @@ func NewFile(r io.ReaderAt, config ...FileConfig) (*File, error) {
f.Loads = append(f.Loads, l)
}
if s != nil {
if int64(s.Offset) < 0 {
return nil, &FormatError{offset, "invalid section offset", s.Offset}
}
if int64(s.Filesz) < 0 {
return nil, &FormatError{offset, "invalid section file size", s.Filesz}
}
// s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
s.ReaderAt = f.sr
}
Expand All @@ -1240,9 +1259,13 @@ func NewFile(r io.ReaderAt, config ...FileConfig) (*File, error) {

func (f *File) parseSymtab(symdat, strtab, cmddat []byte, hdr *types.SymtabCmd, offset int64) (*Symtab, error) {
bo := f.ByteOrder
symtab := make([]Symbol, hdr.Nsyms)
c := saferio.SliceCap[Symbol](uint64(hdr.Nsyms))
if c < 0 {
return nil, &FormatError{offset, "too many symbols", nil}
}
symtab := make([]Symbol, 0, c)
b := bytes.NewReader(symdat)
for i := range symtab {
for i := 0; i < int(hdr.Nsyms); i++ {
var n types.Nlist64
if f.Magic == types.Magic64 {
if err := binary.Read(b, bo, &n); err != nil {
Expand All @@ -1259,20 +1282,21 @@ func (f *File) parseSymtab(symdat, strtab, cmddat []byte, hdr *types.SymtabCmd,
n.Desc = n32.Desc
n.Value = uint64(n32.Value)
}
sym := &symtab[i]
if n.Name >= uint32(len(strtab)) {
return nil, &FormatError{offset, "invalid name in symbol table", n.Name}
}
// We add "_" to Go symbols. Strip it here. See issue 33808.
name := cstring(strtab[n.Name:])
if strings.Contains(name, ".") && name[0] == '_' {
name = name[1:]
var name string
if n.Name < uint32(len(strtab)) {
// We add "_" to Go symbols. Strip it here. See issue 33808.
name = cstring(strtab[n.Name:])
if strings.Contains(name, ".") && name[0] == '_' {
name = name[1:]
}
}
sym.Name = name
sym.Type = n.Type
sym.Sect = n.Sect
sym.Desc = n.Desc
sym.Value = n.Value
symtab = append(symtab, Symbol{
Name: name,
Type: n.Type,
Sect: n.Sect,
Desc: n.Desc,
Value: n.Value,
})
}
st := new(Symtab)
st.LoadBytes = LoadBytes(cmddat)
Expand All @@ -1289,9 +1313,9 @@ func (f *File) pushSection(sh *types.Section, r io.ReaderAt) error {
f.Sections = append(f.Sections, sh)

if sh.Nreloc > 0 {
reldat := make([]byte, int(sh.Nreloc)*8)
if _, err := r.ReadAt(reldat, int64(sh.Reloff)); err != nil {
return fmt.Errorf("failed to read data at Reloff=%#x; %v", int64(sh.Reloff), err)
reldat, err := saferio.ReadDataAt(r, uint64(sh.Nreloc)*8, int64(sh.Reloff))
if err != nil {
return fmt.Errorf("failed to read data at Reloff @ %#x: %w", int64(sh.Reloff), err)
}
b := bytes.NewReader(reldat)

Expand All @@ -1303,7 +1327,7 @@ func (f *File) pushSection(sh *types.Section, r io.ReaderAt) error {

var ri types.RelocInfo
if err := binary.Read(b, bo, &ri); err != nil {
return fmt.Errorf("failed to read relocInfo; %v", err)
return fmt.Errorf("failed to read types.RelocInfo: %w", err)
}

if ri.Addr&(1<<31) != 0 { // scattered
Expand Down
132 changes: 132 additions & 0 deletions internal/saferio/io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package saferio provides I/O functions that avoid allocating large
// amounts of memory unnecessarily. This is intended for packages that
// read data from an [io.Reader] where the size is part of the input
// data but the input may be corrupt, or may be provided by an
// untrustworthy attacker.
package saferio

import (
"io"
"unsafe"
)

// chunk is an arbitrary limit on how much memory we are willing
// to allocate without concern.
const chunk = 10 << 20 // 10M

// ReadData reads n bytes from the input stream, but avoids allocating
// all n bytes if n is large. This avoids crashing the program by
// allocating all n bytes in cases where n is incorrect.
//
// The error is io.EOF only if no bytes were read.
// If an io.EOF happens after reading some but not all the bytes,
// ReadData returns io.ErrUnexpectedEOF.
func ReadData(r io.Reader, n uint64) ([]byte, error) {
if int64(n) < 0 || n != uint64(int(n)) {
// n is too large to fit in int, so we can't allocate
// a buffer large enough. Treat this as a read failure.
return nil, io.ErrUnexpectedEOF
}

if n < chunk {
buf := make([]byte, n)
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, err
}
return buf, nil
}

var buf []byte
buf1 := make([]byte, chunk)
for n > 0 {
next := n
if next > chunk {
next = chunk
}
_, err := io.ReadFull(r, buf1[:next])
if err != nil {
if len(buf) > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return nil, err
}
buf = append(buf, buf1[:next]...)
n -= next
}
return buf, nil
}

// ReadDataAt reads n bytes from the input stream at off, but avoids
// allocating all n bytes if n is large. This avoids crashing the program
// by allocating all n bytes in cases where n is incorrect.
func ReadDataAt(r io.ReaderAt, n uint64, off int64) ([]byte, error) {
if int64(n) < 0 || n != uint64(int(n)) {
// n is too large to fit in int, so we can't allocate
// a buffer large enough. Treat this as a read failure.
return nil, io.ErrUnexpectedEOF
}

if n < chunk {
buf := make([]byte, n)
_, err := r.ReadAt(buf, off)
if err != nil {
// io.SectionReader can return EOF for n == 0,
// but for our purposes that is a success.
if err != io.EOF || n > 0 {
return nil, err
}
}
return buf, nil
}

var buf []byte
buf1 := make([]byte, chunk)
for n > 0 {
next := n
if next > chunk {
next = chunk
}
_, err := r.ReadAt(buf1[:next], off)
if err != nil {
return nil, err
}
buf = append(buf, buf1[:next]...)
n -= next
off += int64(next)
}
return buf, nil
}

// SliceCapWithSize returns the capacity to use when allocating a slice.
// After the slice is allocated with the capacity, it should be
// built using append. This will avoid allocating too much memory
// if the capacity is large and incorrect.
//
// A negative result means that the value is always too big.
func SliceCapWithSize(size, c uint64) int {
if int64(c) < 0 || c != uint64(int(c)) {
return -1
}
if size > 0 && c > (1<<64-1)/size {
return -1
}
if c*size > chunk {
c = chunk / size
if c == 0 {
c = 1
}
}
return int(c)
}

// SliceCap is like SliceCapWithSize but using generics.
func SliceCap[E any](c uint64) int {
var v E
size := uint64(unsafe.Sizeof(v))
return SliceCapWithSize(size, c)
}
2 changes: 1 addition & 1 deletion macho.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// Mach-O header data structures
// Originally at:
// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html (since deleted by Apply)
// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html (since deleted by Apple)
// Archived copy at:
// https://web.archive.org/web/20090819232456/http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/index.html
// For cloned PDF see:
Expand Down
9 changes: 3 additions & 6 deletions types/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"io"
"strings"

"github.com/blacktop/go-macho/internal/saferio"
)

/*
Expand Down Expand Up @@ -483,12 +485,7 @@ func (s *Section) SetReaders(r io.ReaderAt, sr *io.SectionReader) {

// Data reads and returns the contents of the Mach-O section.
func (s *Section) Data() ([]byte, error) {
dat := make([]byte, s.Size)
n, err := s.ReadAt(dat, int64(s.Offset))
if n == len(dat) {
err = nil
}
return dat[0:n], err
return saferio.ReadDataAt(s.sr, s.Size, 0)
}

// Open returns a new ReadSeeker reading the Mach-O section.
Expand Down

0 comments on commit 21cfc1d

Please sign in to comment.