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/*
6Package plan9obj implements access to Plan 9 a.out object files.
7
8# Security
9
10This package is not designed to be hardened against adversarial inputs, and is
11outside the scope of https://go.dev/security/policy. In particular, only basic
12validation is done when parsing object files. As such, care should be taken when
13parsing untrusted inputs, as parsing malformed files may consume significant
14resources, or cause panics.
15*/
16package plan9obj
17
18import (
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.
28type 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.
38type 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.
47type 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.
54type 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.
68func (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.
73func (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.
76type 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.
88type formatError struct {
89	off int
90	msg string
91	val any
92}
93
94func (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.
104func 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.
121func (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
130func 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.
141func 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 := new(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 = make([]*Section, 5)
188
189	off := uint32(f.HdrSize)
190
191	for i, sect := range sects {
192		s := new(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
207func 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.
265func 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 := make(map[uint16]string)
276	syms := make([]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.
314var ErrNoSymbols = errors.New("no symbol section")
315
316// Symbols returns the symbol table for f.
317func (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.
333func (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