file.mx raw

   1  // Copyright 2014 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  /*
   6  Package plan9obj implements access to Plan 9 a.out object files.
   7  
   8  # Security
   9  
  10  This package is not designed to be hardened against adversarial inputs, and is
  11  outside the scope of https://go.dev/security/policy. In particular, only basic
  12  validation is done when parsing object files. As such, care should be taken when
  13  parsing untrusted inputs, as parsing malformed files may consume significant
  14  resources, or cause panics.
  15  */
  16  package plan9obj
  17  
  18  import (
  19  	"encoding/binary"
  20  	"errors"
  21  	"fmt"
  22  	"internal/saferio"
  23  	"io"
  24  	"os"
  25  )
  26  
  27  // A FileHeader represents a Plan 9 a.out file header.
  28  type FileHeader struct {
  29  	Magic       uint32
  30  	Bss         uint32
  31  	Entry       uint64
  32  	PtrSize     int
  33  	LoadAddress uint64
  34  	HdrSize     uint64
  35  }
  36  
  37  // A File represents an open Plan 9 a.out file.
  38  type File struct {
  39  	FileHeader
  40  	Sections []*Section
  41  	closer   io.Closer
  42  }
  43  
  44  // A SectionHeader represents a single Plan 9 a.out section header.
  45  // This structure doesn't exist on-disk, but eases navigation
  46  // through the object file.
  47  type SectionHeader struct {
  48  	Name   string
  49  	Size   uint32
  50  	Offset uint32
  51  }
  52  
  53  // A Section represents a single section in a Plan 9 a.out file.
  54  type Section struct {
  55  	SectionHeader
  56  
  57  	// Embed ReaderAt for ReadAt method.
  58  	// Do not embed SectionReader directly
  59  	// to avoid having Read and Seek.
  60  	// If a client wants Read and Seek it must use
  61  	// Open() to avoid fighting over the seek offset
  62  	// with other clients.
  63  	io.ReaderAt
  64  	sr *io.SectionReader
  65  }
  66  
  67  // Data reads and returns the contents of the Plan 9 a.out section.
  68  func (s *Section) Data() ([]byte, error) {
  69  	return saferio.ReadDataAt(s.sr, uint64(s.Size), 0)
  70  }
  71  
  72  // Open returns a new ReadSeeker reading the Plan 9 a.out section.
  73  func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
  74  
  75  // A Symbol represents an entry in a Plan 9 a.out symbol table section.
  76  type Sym struct {
  77  	Value uint64
  78  	Type  rune
  79  	Name  string
  80  }
  81  
  82  /*
  83   * Plan 9 a.out reader
  84   */
  85  
  86  // formatError is returned by some operations if the data does
  87  // not have the correct format for an object file.
  88  type formatError struct {
  89  	off int
  90  	msg string
  91  	val any
  92  }
  93  
  94  func (e *formatError) Error() string {
  95  	msg := e.msg
  96  	if e.val != nil {
  97  		msg += fmt.Sprintf(" '%v'", e.val)
  98  	}
  99  	msg += fmt.Sprintf(" in record at byte %#x", e.off)
 100  	return msg
 101  }
 102  
 103  // Open opens the named file using [os.Open] and prepares it for use as a Plan 9 a.out binary.
 104  func Open(name string) (*File, error) {
 105  	f, err := os.Open(name)
 106  	if err != nil {
 107  		return nil, err
 108  	}
 109  	ff, err := NewFile(f)
 110  	if err != nil {
 111  		f.Close()
 112  		return nil, err
 113  	}
 114  	ff.closer = f
 115  	return ff, nil
 116  }
 117  
 118  // Close closes the [File].
 119  // If the [File] was created using [NewFile] directly instead of [Open],
 120  // Close has no effect.
 121  func (f *File) Close() error {
 122  	var err error
 123  	if f.closer != nil {
 124  		err = f.closer.Close()
 125  		f.closer = nil
 126  	}
 127  	return err
 128  }
 129  
 130  func parseMagic(magic []byte) (uint32, error) {
 131  	m := binary.BigEndian.Uint32(magic)
 132  	switch m {
 133  	case Magic386, MagicAMD64, MagicARM:
 134  		return m, nil
 135  	}
 136  	return 0, &formatError{0, "bad magic number", magic}
 137  }
 138  
 139  // NewFile creates a new [File] for accessing a Plan 9 binary in an underlying reader.
 140  // The Plan 9 binary is expected to start at position 0 in the ReaderAt.
 141  func NewFile(r io.ReaderAt) (*File, error) {
 142  	sr := io.NewSectionReader(r, 0, 1<<63-1)
 143  	// Read and decode Plan 9 magic
 144  	var magic [4]byte
 145  	if _, err := r.ReadAt(magic[:], 0); err != nil {
 146  		return nil, err
 147  	}
 148  	_, err := parseMagic(magic[:])
 149  	if err != nil {
 150  		return nil, err
 151  	}
 152  
 153  	ph := &prog{}
 154  	if err := binary.Read(sr, binary.BigEndian, ph); err != nil {
 155  		return nil, err
 156  	}
 157  
 158  	f := &File{FileHeader: FileHeader{
 159  		Magic:       ph.Magic,
 160  		Bss:         ph.Bss,
 161  		Entry:       uint64(ph.Entry),
 162  		PtrSize:     4,
 163  		LoadAddress: 0x1000,
 164  		HdrSize:     4 * 8,
 165  	}}
 166  
 167  	if ph.Magic&Magic64 != 0 {
 168  		if err := binary.Read(sr, binary.BigEndian, &f.Entry); err != nil {
 169  			return nil, err
 170  		}
 171  		f.PtrSize = 8
 172  		f.LoadAddress = 0x200000
 173  		f.HdrSize += 8
 174  	}
 175  
 176  	var sects = []struct {
 177  		name string
 178  		size uint32
 179  	}{
 180  		{"text", ph.Text},
 181  		{"data", ph.Data},
 182  		{"syms", ph.Syms},
 183  		{"spsz", ph.Spsz},
 184  		{"pcsz", ph.Pcsz},
 185  	}
 186  
 187  	f.Sections = []*Section{:5}
 188  
 189  	off := uint32(f.HdrSize)
 190  
 191  	for i, sect := range sects {
 192  		s := &Section{}
 193  		s.SectionHeader = SectionHeader{
 194  			Name:   sect.name,
 195  			Size:   sect.size,
 196  			Offset: off,
 197  		}
 198  		off += sect.size
 199  		s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Size))
 200  		s.ReaderAt = s.sr
 201  		f.Sections[i] = s
 202  	}
 203  
 204  	return f, nil
 205  }
 206  
 207  func walksymtab(data []byte, ptrsz int, fn func(sym) error) error {
 208  	var order binary.ByteOrder = binary.BigEndian
 209  	var s sym
 210  	p := data
 211  	for len(p) >= 4 {
 212  		// Symbol type, value.
 213  		if len(p) < ptrsz {
 214  			return &formatError{len(data), "unexpected EOF", nil}
 215  		}
 216  		// fixed-width value
 217  		if ptrsz == 8 {
 218  			s.value = order.Uint64(p[0:8])
 219  			p = p[8:]
 220  		} else {
 221  			s.value = uint64(order.Uint32(p[0:4]))
 222  			p = p[4:]
 223  		}
 224  
 225  		if len(p) < 1 {
 226  			return &formatError{len(data), "unexpected EOF", nil}
 227  		}
 228  		typ := p[0] & 0x7F
 229  		s.typ = typ
 230  		p = p[1:]
 231  
 232  		// Name.
 233  		var i int
 234  		var nnul int
 235  		for i = 0; i < len(p); i++ {
 236  			if p[i] == 0 {
 237  				nnul = 1
 238  				break
 239  			}
 240  		}
 241  		switch typ {
 242  		case 'z', 'Z':
 243  			p = p[i+nnul:]
 244  			for i = 0; i+2 <= len(p); i += 2 {
 245  				if p[i] == 0 && p[i+1] == 0 {
 246  					nnul = 2
 247  					break
 248  				}
 249  			}
 250  		}
 251  		if len(p) < i+nnul {
 252  			return &formatError{len(data), "unexpected EOF", nil}
 253  		}
 254  		s.name = p[0:i]
 255  		i += nnul
 256  		p = p[i:]
 257  
 258  		fn(s)
 259  	}
 260  	return nil
 261  }
 262  
 263  // newTable decodes the Go symbol table in data,
 264  // returning an in-memory representation.
 265  func newTable(symtab []byte, ptrsz int) ([]Sym, error) {
 266  	var n int
 267  	err := walksymtab(symtab, ptrsz, func(s sym) error {
 268  		n++
 269  		return nil
 270  	})
 271  	if err != nil {
 272  		return nil, err
 273  	}
 274  
 275  	fname := map[uint16]string{}
 276  	syms := []Sym{:0:n}
 277  	err = walksymtab(symtab, ptrsz, func(s sym) error {
 278  		n := len(syms)
 279  		syms = syms[0 : n+1]
 280  		ts := &syms[n]
 281  		ts.Type = rune(s.typ)
 282  		ts.Value = s.value
 283  		switch s.typ {
 284  		default:
 285  			ts.Name = string(s.name)
 286  		case 'z', 'Z':
 287  			for i := 0; i < len(s.name); i += 2 {
 288  				eltIdx := binary.BigEndian.Uint16(s.name[i : i+2])
 289  				elt, ok := fname[eltIdx]
 290  				if !ok {
 291  					return &formatError{-1, "bad filename code", eltIdx}
 292  				}
 293  				if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' {
 294  					ts.Name += "/"
 295  				}
 296  				ts.Name += elt
 297  			}
 298  		}
 299  		switch s.typ {
 300  		case 'f':
 301  			fname[uint16(s.value)] = ts.Name
 302  		}
 303  		return nil
 304  	})
 305  	if err != nil {
 306  		return nil, err
 307  	}
 308  
 309  	return syms, nil
 310  }
 311  
 312  // ErrNoSymbols is returned by [File.Symbols] if there is no such section
 313  // in the File.
 314  var ErrNoSymbols = errors.New("no symbol section")
 315  
 316  // Symbols returns the symbol table for f.
 317  func (f *File) Symbols() ([]Sym, error) {
 318  	symtabSection := f.Section("syms")
 319  	if symtabSection == nil {
 320  		return nil, ErrNoSymbols
 321  	}
 322  
 323  	symtab, err := symtabSection.Data()
 324  	if err != nil {
 325  		return nil, errors.New("cannot load symbol section")
 326  	}
 327  
 328  	return newTable(symtab, f.PtrSize)
 329  }
 330  
 331  // Section returns a section with the given name, or nil if no such
 332  // section exists.
 333  func (f *File) Section(name string) *Section {
 334  	for _, s := range f.Sections {
 335  		if s.Name == name {
 336  			return s
 337  		}
 338  	}
 339  	return nil
 340  }
 341