buildinfo.mx raw

   1  // Copyright 2021 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  // Package buildinfo provides access to information embedded in a Go binary
   6  // about how it was built. This includes the Go toolchain version, and the
   7  // set of modules used (for binaries built in module mode).
   8  //
   9  // Build information is available for the currently running binary in
  10  // runtime/debug.ReadBuildInfo.
  11  package buildinfo
  12  
  13  import (
  14  	"bytes"
  15  	"debug/elf"
  16  	"debug/macho"
  17  	"debug/pe"
  18  	"debug/plan9obj"
  19  	"encoding/binary"
  20  	"errors"
  21  	"fmt"
  22  	"internal/saferio"
  23  	"internal/xcoff"
  24  	"io"
  25  	"io/fs"
  26  	"os"
  27  	"runtime/debug"
  28  	_ "unsafe" // for linkname
  29  )
  30  
  31  // Type alias for build info. We cannot move the types here, since
  32  // runtime/debug would need to import this package, which would make it
  33  // a much larger dependency.
  34  type BuildInfo = debug.BuildInfo
  35  
  36  // errUnrecognizedFormat is returned when a given executable file doesn't
  37  // appear to be in a known format, or it breaks the rules of that format,
  38  // or when there are I/O errors reading the file.
  39  var errUnrecognizedFormat = errors.New("unrecognized file format")
  40  
  41  // errNotGoExe is returned when a given executable file is valid but does
  42  // not contain Go build information.
  43  //
  44  // errNotGoExe should be an internal detail,
  45  // but widely used packages access it using linkname.
  46  // Notable members of the hall of shame include:
  47  //   - github.com/quay/claircore
  48  //
  49  // Do not remove or change the type signature.
  50  // See go.dev/issue/67401.
  51  //
  52  //go:linkname errNotGoExe
  53  var errNotGoExe = errors.New("not a Go executable")
  54  
  55  // The build info blob left by the linker is identified by a 32-byte header,
  56  // consisting of buildInfoMagic (14 bytes), followed by version-dependent
  57  // fields.
  58  var buildInfoMagic = []byte("\xff Go buildinf:")
  59  
  60  const (
  61  	buildInfoAlign      = 16
  62  	buildInfoHeaderSize = 32
  63  )
  64  
  65  // ReadFile returns build information embedded in a Go binary
  66  // file at the given path. Most information is only available for binaries built
  67  // with module support.
  68  func ReadFile(name string) (info *BuildInfo, err error) {
  69  	defer func() {
  70  		if _, ok := err.(*fs.PathError); ok {
  71  			err = fmt.Errorf("could not read Go build info: %w", err)
  72  		} else if err != nil {
  73  			err = fmt.Errorf("could not read Go build info from %s: %w", name, err)
  74  		}
  75  	}()
  76  
  77  	f, err := os.Open(name)
  78  	if err != nil {
  79  		return nil, err
  80  	}
  81  	defer f.Close()
  82  	return Read(f)
  83  }
  84  
  85  // Read returns build information embedded in a Go binary file
  86  // accessed through the given ReaderAt. Most information is only available for
  87  // binaries built with module support.
  88  func Read(r io.ReaderAt) (*BuildInfo, error) {
  89  	vers, mod, err := readRawBuildInfo(r)
  90  	if err != nil {
  91  		return nil, err
  92  	}
  93  	bi, err := debug.ParseBuildInfo(mod)
  94  	if err != nil {
  95  		return nil, err
  96  	}
  97  	bi.GoVersion = vers
  98  	return bi, nil
  99  }
 100  
 101  type exe interface {
 102  	// DataStart returns the virtual address and size of the segment or section that
 103  	// should contain build information. This is either a specially named section
 104  	// or the first writable non-zero data segment.
 105  	DataStart() (uint64, uint64)
 106  
 107  	// DataReader returns an io.ReaderAt that reads from addr until the end
 108  	// of segment or section that contains addr.
 109  	DataReader(addr uint64) (io.ReaderAt, error)
 110  }
 111  
 112  // readRawBuildInfo extracts the Go toolchain version and module information
 113  // strings from a Go binary. On success, vers should be non-empty. mod
 114  // is empty if the binary was not built with modules enabled.
 115  func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) {
 116  	// Read the first bytes of the file to identify the format, then delegate to
 117  	// a format-specific function to load segment and section headers.
 118  	ident := []byte{:16}
 119  	if n, err := r.ReadAt(ident, 0); n < len(ident) || err != nil {
 120  		return "", "", errUnrecognizedFormat
 121  	}
 122  
 123  	var x exe
 124  	switch {
 125  	case bytes.HasPrefix(ident, []byte("\x7FELF")):
 126  		f, err := elf.NewFile(r)
 127  		if err != nil {
 128  			return "", "", errUnrecognizedFormat
 129  		}
 130  		x = &elfExe{f}
 131  	case bytes.HasPrefix(ident, []byte("MZ")):
 132  		f, err := pe.NewFile(r)
 133  		if err != nil {
 134  			return "", "", errUnrecognizedFormat
 135  		}
 136  		x = &peExe{f}
 137  	case bytes.HasPrefix(ident, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(ident[1:], []byte("\xFA\xED\xFE")):
 138  		f, err := macho.NewFile(r)
 139  		if err != nil {
 140  			return "", "", errUnrecognizedFormat
 141  		}
 142  		x = &machoExe{f}
 143  	case bytes.HasPrefix(ident, []byte("\xCA\xFE\xBA\xBE")) || bytes.HasPrefix(ident, []byte("\xCA\xFE\xBA\xBF")):
 144  		f, err := macho.NewFatFile(r)
 145  		if err != nil || len(f.Arches) == 0 {
 146  			return "", "", errUnrecognizedFormat
 147  		}
 148  		x = &machoExe{f.Arches[0].File}
 149  	case bytes.HasPrefix(ident, []byte{0x01, 0xDF}) || bytes.HasPrefix(ident, []byte{0x01, 0xF7}):
 150  		f, err := xcoff.NewFile(r)
 151  		if err != nil {
 152  			return "", "", errUnrecognizedFormat
 153  		}
 154  		x = &xcoffExe{f}
 155  	case hasPlan9Magic(ident):
 156  		f, err := plan9obj.NewFile(r)
 157  		if err != nil {
 158  			return "", "", errUnrecognizedFormat
 159  		}
 160  		x = &plan9objExe{f}
 161  	default:
 162  		return "", "", errUnrecognizedFormat
 163  	}
 164  
 165  	// Read segment or section to find the build info blob.
 166  	// On some platforms, the blob will be in its own section, and DataStart
 167  	// returns the address of that section. On others, it's somewhere in the
 168  	// data segment; the linker puts it near the beginning.
 169  	// See cmd/link/internal/ld.Link.buildinfo.
 170  	dataAddr, dataSize := x.DataStart()
 171  	if dataSize == 0 {
 172  		return "", "", errNotGoExe
 173  	}
 174  
 175  	addr, err := searchMagic(x, dataAddr, dataSize)
 176  	if err != nil {
 177  		return "", "", err
 178  	}
 179  
 180  	// Read in the full header first.
 181  	header, err := readData(x, addr, buildInfoHeaderSize)
 182  	if err == io.EOF {
 183  		return "", "", errNotGoExe
 184  	} else if err != nil {
 185  		return "", "", err
 186  	}
 187  	if len(header) < buildInfoHeaderSize {
 188  		return "", "", errNotGoExe
 189  	}
 190  
 191  	const (
 192  		ptrSizeOffset = 14
 193  		flagsOffset   = 15
 194  		versPtrOffset = 16
 195  
 196  		flagsEndianMask   = 0x1
 197  		flagsEndianLittle = 0x0
 198  		flagsEndianBig    = 0x1
 199  
 200  		flagsVersionMask = 0x2
 201  		flagsVersionPtr  = 0x0
 202  		flagsVersionInl  = 0x2
 203  	)
 204  
 205  	// Decode the blob. The blob is a 32-byte header, optionally followed
 206  	// by 2 varint-prefixed string contents.
 207  	//
 208  	// type buildInfoHeader struct {
 209  	// 	magic       [14]byte
 210  	// 	ptrSize     uint8 // used if flagsVersionPtr
 211  	// 	flags       uint8
 212  	// 	versPtr     targetUintptr // used if flagsVersionPtr
 213  	// 	modPtr      targetUintptr // used if flagsVersionPtr
 214  	// }
 215  	//
 216  	// The version bit of the flags field determines the details of the format.
 217  	//
 218  	// Prior to 1.18, the flags version bit is flagsVersionPtr. In this
 219  	// case, the header includes pointers to the version and modinfo Go
 220  	// strings in the header. The ptrSize field indicates the size of the
 221  	// pointers and the endian bit of the flag indicates the pointer
 222  	// endianness.
 223  	//
 224  	// Since 1.18, the flags version bit is flagsVersionInl. In this case,
 225  	// the header is followed by the string contents inline as
 226  	// length-prefixed (as varint) string contents. First is the version
 227  	// string, followed immediately by the modinfo string.
 228  	flags := header[flagsOffset]
 229  	if flags&flagsVersionMask == flagsVersionInl {
 230  		vers, addr, err = decodeString(x, addr+buildInfoHeaderSize)
 231  		if err != nil {
 232  			return "", "", err
 233  		}
 234  		mod, _, err = decodeString(x, addr)
 235  		if err != nil {
 236  			return "", "", err
 237  		}
 238  	} else {
 239  		// flagsVersionPtr (<1.18)
 240  		ptrSize := int(header[ptrSizeOffset])
 241  		bigEndian := flags&flagsEndianMask == flagsEndianBig
 242  		var bo binary.ByteOrder
 243  		if bigEndian {
 244  			bo = binary.BigEndian
 245  		} else {
 246  			bo = binary.LittleEndian
 247  		}
 248  		var readPtr func([]byte) uint64
 249  		if ptrSize == 4 {
 250  			readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) }
 251  		} else if ptrSize == 8 {
 252  			readPtr = bo.Uint64
 253  		} else {
 254  			return "", "", errNotGoExe
 255  		}
 256  		vers = readString(x, ptrSize, readPtr, readPtr(header[versPtrOffset:]))
 257  		mod = readString(x, ptrSize, readPtr, readPtr(header[versPtrOffset+ptrSize:]))
 258  	}
 259  	if vers == "" {
 260  		return "", "", errNotGoExe
 261  	}
 262  	if len(mod) >= 33 && mod[len(mod)-17] == '\n' {
 263  		// Strip module framing: sentinel strings delimiting the module info.
 264  		// These are cmd/go/internal/modload.infoStart and infoEnd.
 265  		mod = mod[16 : len(mod)-16]
 266  	} else {
 267  		mod = ""
 268  	}
 269  
 270  	return vers, mod, nil
 271  }
 272  
 273  func hasPlan9Magic(magic []byte) bool {
 274  	if len(magic) >= 4 {
 275  		m := binary.BigEndian.Uint32(magic)
 276  		switch m {
 277  		case plan9obj.Magic386, plan9obj.MagicAMD64, plan9obj.MagicARM:
 278  			return true
 279  		}
 280  	}
 281  	return false
 282  }
 283  
 284  func decodeString(x exe, addr uint64) (string, uint64, error) {
 285  	// varint length followed by length bytes of data.
 286  
 287  	// N.B. ReadData reads _up to_ size bytes from the section containing
 288  	// addr. So we don't need to check that size doesn't overflow the
 289  	// section.
 290  	b, err := readData(x, addr, binary.MaxVarintLen64)
 291  	if err == io.EOF {
 292  		return "", 0, errNotGoExe
 293  	} else if err != nil {
 294  		return "", 0, err
 295  	}
 296  
 297  	length, n := binary.Uvarint(b)
 298  	if n <= 0 {
 299  		return "", 0, errNotGoExe
 300  	}
 301  	addr += uint64(n)
 302  
 303  	b, err = readData(x, addr, length)
 304  	if err == io.EOF {
 305  		return "", 0, errNotGoExe
 306  	} else if err == io.ErrUnexpectedEOF {
 307  		// Length too large to allocate. Clearly bogus value.
 308  		return "", 0, errNotGoExe
 309  	} else if err != nil {
 310  		return "", 0, err
 311  	}
 312  	if uint64(len(b)) < length {
 313  		// Section ended before we could read the full string.
 314  		return "", 0, errNotGoExe
 315  	}
 316  
 317  	return string(b), addr + length, nil
 318  }
 319  
 320  // readString returns the string at address addr in the executable x.
 321  func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string {
 322  	hdr, err := readData(x, addr, uint64(2*ptrSize))
 323  	if err != nil || len(hdr) < 2*ptrSize {
 324  		return ""
 325  	}
 326  	dataAddr := readPtr(hdr)
 327  	dataLen := readPtr(hdr[ptrSize:])
 328  	data, err := readData(x, dataAddr, dataLen)
 329  	if err != nil || uint64(len(data)) < dataLen {
 330  		return ""
 331  	}
 332  	return string(data)
 333  }
 334  
 335  const searchChunkSize = 1 << 20 // 1 MB
 336  
 337  // searchMagic returns the aligned first instance of buildInfoMagic in the data
 338  // range [addr, addr+size). Returns false if not found.
 339  func searchMagic(x exe, start, size uint64) (uint64, error) {
 340  	end := start + size
 341  	if end < start {
 342  		// Overflow.
 343  		return 0, errUnrecognizedFormat
 344  	}
 345  
 346  	// Round up start; magic can't occur in the initial unaligned portion.
 347  	start = (start + buildInfoAlign - 1) &^ (buildInfoAlign - 1)
 348  	if start >= end {
 349  		return 0, errNotGoExe
 350  	}
 351  
 352  	var buf []byte
 353  	for start < end {
 354  		// Read in chunks to avoid consuming too much memory if data is large.
 355  		//
 356  		// Normally it would be somewhat painful to handle the magic crossing a
 357  		// chunk boundary, but since it must be 16-byte aligned we know it will
 358  		// fall within a single chunk.
 359  		remaining := end - start
 360  		chunkSize := uint64(searchChunkSize)
 361  		if chunkSize > remaining {
 362  			chunkSize = remaining
 363  		}
 364  
 365  		if buf == nil {
 366  			buf = []byte{:chunkSize}
 367  		} else {
 368  			// N.B. chunkSize can only decrease, and only on the
 369  			// last chunk.
 370  			buf = buf[:chunkSize]
 371  			clear(buf)
 372  		}
 373  
 374  		n, err := readDataInto(x, start, buf)
 375  		if err == io.EOF {
 376  			// EOF before finding the magic; must not be a Go executable.
 377  			return 0, errNotGoExe
 378  		} else if err != nil {
 379  			return 0, err
 380  		}
 381  
 382  		data := buf[:n]
 383  		for len(data) > 0 {
 384  			i := bytes.Index(data, buildInfoMagic)
 385  			if i < 0 {
 386  				break
 387  			}
 388  			if remaining-uint64(i) < buildInfoHeaderSize {
 389  				// Found magic, but not enough space left for the full header.
 390  				return 0, errNotGoExe
 391  			}
 392  			if i%buildInfoAlign != 0 {
 393  				// Found magic, but misaligned. Keep searching.
 394  				next := (i + buildInfoAlign - 1) &^ (buildInfoAlign - 1)
 395  				if next > len(data) {
 396  					// Corrupt object file: the remaining
 397  					// count says there is more data,
 398  					// but we didn't read it.
 399  					return 0, errNotGoExe
 400  				}
 401  				data = data[next:]
 402  				continue
 403  			}
 404  			// Good match!
 405  			return start + uint64(i), nil
 406  		}
 407  
 408  		start += chunkSize
 409  	}
 410  
 411  	return 0, errNotGoExe
 412  }
 413  
 414  func readData(x exe, addr, size uint64) ([]byte, error) {
 415  	r, err := x.DataReader(addr)
 416  	if err != nil {
 417  		return nil, err
 418  	}
 419  
 420  	b, err := saferio.ReadDataAt(r, size, 0)
 421  	if len(b) > 0 && err == io.EOF {
 422  		err = nil
 423  	}
 424  	return b, err
 425  }
 426  
 427  func readDataInto(x exe, addr uint64, b []byte) (int, error) {
 428  	r, err := x.DataReader(addr)
 429  	if err != nil {
 430  		return 0, err
 431  	}
 432  
 433  	n, err := r.ReadAt(b, 0)
 434  	if n > 0 && err == io.EOF {
 435  		err = nil
 436  	}
 437  	return n, err
 438  }
 439  
 440  // elfExe is the ELF implementation of the exe interface.
 441  type elfExe struct {
 442  	f *elf.File
 443  }
 444  
 445  func (x *elfExe) DataReader(addr uint64) (io.ReaderAt, error) {
 446  	for _, prog := range x.f.Progs {
 447  		if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
 448  			remaining := prog.Vaddr + prog.Filesz - addr
 449  			return io.NewSectionReader(prog, int64(addr-prog.Vaddr), int64(remaining)), nil
 450  		}
 451  	}
 452  	return nil, errUnrecognizedFormat
 453  }
 454  
 455  func (x *elfExe) DataStart() (uint64, uint64) {
 456  	for _, s := range x.f.Sections {
 457  		if s.Name == ".go.buildinfo" {
 458  			return s.Addr, s.Size
 459  		}
 460  	}
 461  	for _, p := range x.f.Progs {
 462  		if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
 463  			return p.Vaddr, p.Memsz
 464  		}
 465  	}
 466  	return 0, 0
 467  }
 468  
 469  // peExe is the PE (Windows Portable Executable) implementation of the exe interface.
 470  type peExe struct {
 471  	f *pe.File
 472  }
 473  
 474  func (x *peExe) imageBase() uint64 {
 475  	switch oh := x.f.OptionalHeader.(type) {
 476  	case *pe.OptionalHeader32:
 477  		return uint64(oh.ImageBase)
 478  	case *pe.OptionalHeader64:
 479  		return oh.ImageBase
 480  	}
 481  	return 0
 482  }
 483  
 484  func (x *peExe) DataReader(addr uint64) (io.ReaderAt, error) {
 485  	addr -= x.imageBase()
 486  	for _, sect := range x.f.Sections {
 487  		if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
 488  			remaining := uint64(sect.VirtualAddress+sect.Size) - addr
 489  			return io.NewSectionReader(sect, int64(addr-uint64(sect.VirtualAddress)), int64(remaining)), nil
 490  		}
 491  	}
 492  	return nil, errUnrecognizedFormat
 493  }
 494  
 495  func (x *peExe) DataStart() (uint64, uint64) {
 496  	// Assume data is first writable section.
 497  	const (
 498  		IMAGE_SCN_CNT_CODE               = 0x00000020
 499  		IMAGE_SCN_CNT_INITIALIZED_DATA   = 0x00000040
 500  		IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
 501  		IMAGE_SCN_MEM_EXECUTE            = 0x20000000
 502  		IMAGE_SCN_MEM_READ               = 0x40000000
 503  		IMAGE_SCN_MEM_WRITE              = 0x80000000
 504  		IMAGE_SCN_MEM_DISCARDABLE        = 0x2000000
 505  		IMAGE_SCN_LNK_NRELOC_OVFL        = 0x1000000
 506  		IMAGE_SCN_ALIGN_32BYTES          = 0x600000
 507  	)
 508  	for _, sect := range x.f.Sections {
 509  		if sect.VirtualAddress != 0 && sect.Size != 0 &&
 510  			sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
 511  			return uint64(sect.VirtualAddress) + x.imageBase(), uint64(sect.VirtualSize)
 512  		}
 513  	}
 514  	return 0, 0
 515  }
 516  
 517  // machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface.
 518  type machoExe struct {
 519  	f *macho.File
 520  }
 521  
 522  func (x *machoExe) DataReader(addr uint64) (io.ReaderAt, error) {
 523  	for _, load := range x.f.Loads {
 524  		seg, ok := load.(*macho.Segment)
 525  		if !ok {
 526  			continue
 527  		}
 528  		if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
 529  			if seg.Name == "__PAGEZERO" {
 530  				continue
 531  			}
 532  			remaining := seg.Addr + seg.Filesz - addr
 533  			return io.NewSectionReader(seg, int64(addr-seg.Addr), int64(remaining)), nil
 534  		}
 535  	}
 536  	return nil, errUnrecognizedFormat
 537  }
 538  
 539  func (x *machoExe) DataStart() (uint64, uint64) {
 540  	// Look for section named "__go_buildinfo".
 541  	for _, sec := range x.f.Sections {
 542  		if sec.Name == "__go_buildinfo" {
 543  			return sec.Addr, sec.Size
 544  		}
 545  	}
 546  	// Try the first non-empty writable segment.
 547  	const RW = 3
 548  	for _, load := range x.f.Loads {
 549  		seg, ok := load.(*macho.Segment)
 550  		if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
 551  			return seg.Addr, seg.Memsz
 552  		}
 553  	}
 554  	return 0, 0
 555  }
 556  
 557  // xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface.
 558  type xcoffExe struct {
 559  	f *xcoff.File
 560  }
 561  
 562  func (x *xcoffExe) DataReader(addr uint64) (io.ReaderAt, error) {
 563  	for _, sect := range x.f.Sections {
 564  		if sect.VirtualAddress <= addr && addr <= sect.VirtualAddress+sect.Size-1 {
 565  			remaining := sect.VirtualAddress + sect.Size - addr
 566  			return io.NewSectionReader(sect, int64(addr-sect.VirtualAddress), int64(remaining)), nil
 567  		}
 568  	}
 569  	return nil, errors.New("address not mapped")
 570  }
 571  
 572  func (x *xcoffExe) DataStart() (uint64, uint64) {
 573  	if s := x.f.SectionByType(xcoff.STYP_DATA); s != nil {
 574  		return s.VirtualAddress, s.Size
 575  	}
 576  	return 0, 0
 577  }
 578  
 579  // plan9objExe is the Plan 9 a.out implementation of the exe interface.
 580  type plan9objExe struct {
 581  	f *plan9obj.File
 582  }
 583  
 584  func (x *plan9objExe) DataStart() (uint64, uint64) {
 585  	if s := x.f.Section("data"); s != nil {
 586  		return uint64(s.Offset), uint64(s.Size)
 587  	}
 588  	return 0, 0
 589  }
 590  
 591  func (x *plan9objExe) DataReader(addr uint64) (io.ReaderAt, error) {
 592  	for _, sect := range x.f.Sections {
 593  		if uint64(sect.Offset) <= addr && addr <= uint64(sect.Offset+sect.Size-1) {
 594  			remaining := uint64(sect.Offset+sect.Size) - addr
 595  			return io.NewSectionReader(sect, int64(addr-uint64(sect.Offset)), int64(remaining)), nil
 596  		}
 597  	}
 598  	return nil, errors.New("address not mapped")
 599  }
 600