// Copyright 2009 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 macho implements access to Mach-O object files. # Security This package is not designed to be hardened against adversarial inputs, and is outside the scope of https://go.dev/security/policy. In particular, only basic validation is done when parsing object files. As such, care should be taken when parsing untrusted inputs, as parsing malformed files may consume significant resources, or cause panics. */
package macho // High level access to low level data structures. import ( ) // A File represents an open Mach-O file. type File struct { FileHeader ByteOrder binary.ByteOrder Loads []Load Sections []*Section Symtab *Symtab Dysymtab *Dysymtab closer io.Closer } // A Load represents any Mach-O load command. type Load interface { Raw() []byte } // A LoadBytes is the uninterpreted bytes of a Mach-O load command. type LoadBytes []byte func ( LoadBytes) () []byte { return } // A SegmentHeader is the header for a Mach-O 32-bit or 64-bit load segment command. type SegmentHeader struct { Cmd LoadCmd Len uint32 Name string Addr uint64 Memsz uint64 Offset uint64 Filesz uint64 Maxprot uint32 Prot uint32 Nsect uint32 Flag uint32 } // A Segment represents a Mach-O 32-bit or 64-bit load segment command. type Segment struct { LoadBytes SegmentHeader // Embed ReaderAt for ReadAt method. // Do not embed SectionReader directly // to avoid having Read and Seek. // If a client wants Read and Seek it must use // Open() to avoid fighting over the seek offset // with other clients. io.ReaderAt sr *io.SectionReader } // Data reads and returns the contents of the segment. func ( *Segment) () ([]byte, error) { return saferio.ReadDataAt(.sr, .Filesz, 0) } // Open returns a new ReadSeeker reading the segment. func ( *Segment) () io.ReadSeeker { return io.NewSectionReader(.sr, 0, 1<<63-1) } type SectionHeader struct { Name string Seg string Addr uint64 Size uint64 Offset uint32 Align uint32 Reloff uint32 Nreloc uint32 Flags uint32 } // A Reloc represents a Mach-O relocation. type Reloc struct { Addr uint32 Value uint32 // when Scattered == false && Extern == true, Value is the symbol number. // when Scattered == false && Extern == false, Value is the section number. // when Scattered == true, Value is the value that this reloc refers to. Type uint8 Len uint8 // 0=byte, 1=word, 2=long, 3=quad Pcrel bool Extern bool // valid if Scattered == false Scattered bool } type Section struct { SectionHeader Relocs []Reloc // Embed ReaderAt for ReadAt method. // Do not embed SectionReader directly // to avoid having Read and Seek. // If a client wants Read and Seek it must use // Open() to avoid fighting over the seek offset // with other clients. io.ReaderAt sr *io.SectionReader } // Data reads and returns the contents of the Mach-O section. func ( *Section) () ([]byte, error) { return saferio.ReadDataAt(.sr, .Size, 0) } // Open returns a new ReadSeeker reading the Mach-O section. func ( *Section) () io.ReadSeeker { return io.NewSectionReader(.sr, 0, 1<<63-1) } // A Dylib represents a Mach-O load dynamic library command. type Dylib struct { LoadBytes Name string Time uint32 CurrentVersion uint32 CompatVersion uint32 } // A Symtab represents a Mach-O symbol table command. type Symtab struct { LoadBytes SymtabCmd Syms []Symbol } // A Dysymtab represents a Mach-O dynamic symbol table command. type Dysymtab struct { LoadBytes DysymtabCmd IndirectSyms []uint32 // indices into Symtab.Syms } // A Rpath represents a Mach-O rpath command. type Rpath struct { LoadBytes Path string } // A Symbol is a Mach-O 32-bit or 64-bit symbol table entry. type Symbol struct { Name string Type uint8 Sect uint8 Desc uint16 Value uint64 } /* * Mach-O reader */ // FormatError is returned by some operations if the data does // not have the correct format for an object file. type FormatError struct { off int64 msg string val any } func ( *FormatError) () string { := .msg if .val != nil { += fmt.Sprintf(" '%v'", .val) } += fmt.Sprintf(" in record at byte %#x", .off) return } // Open opens the named file using [os.Open] and prepares it for use as a Mach-O binary. func ( string) (*File, error) { , := os.Open() if != nil { return nil, } , := NewFile() if != nil { .Close() return nil, } .closer = return , nil } // Close closes the [File]. // If the [File] was created using [NewFile] directly instead of [Open], // Close has no effect. func ( *File) () error { var error if .closer != nil { = .closer.Close() .closer = nil } return } // NewFile creates a new [File] for accessing a Mach-O binary in an underlying reader. // The Mach-O binary is expected to start at position 0 in the ReaderAt. func ( io.ReaderAt) (*File, error) { := new(File) := io.NewSectionReader(, 0, 1<<63-1) // Read and decode Mach magic to determine byte order, size. // Magic32 and Magic64 differ only in the bottom bit. var [4]byte if , := .ReadAt([0:], 0); != nil { return nil, } := binary.BigEndian.Uint32([0:]) := binary.LittleEndian.Uint32([0:]) switch Magic32 &^ 1 { case &^ 1: .ByteOrder = binary.BigEndian .Magic = case &^ 1: .ByteOrder = binary.LittleEndian .Magic = default: return nil, &FormatError{0, "invalid magic number", nil} } // Read entire file header. if := binary.Read(, .ByteOrder, &.FileHeader); != nil { return nil, } // Then load commands. := int64(fileHeaderSize32) if .Magic == Magic64 { = fileHeaderSize64 } , := saferio.ReadDataAt(, uint64(.Cmdsz), ) if != nil { return nil, } := saferio.SliceCap[Load](uint64(.Ncmd)) if < 0 { return nil, &FormatError{, "too many load commands", nil} } .Loads = make([]Load, 0, ) := .ByteOrder for := uint32(0); < .Ncmd; ++ { // Each load command begins with uint32 command and length. if len() < 8 { return nil, &FormatError{, "command block too small", nil} } , := LoadCmd(.Uint32([0:4])), .Uint32([4:8]) if < 8 || > uint32(len()) { return nil, &FormatError{, "invalid command block size", nil} } var []byte , = [0:], [:] += int64() var *Segment switch { default: .Loads = append(.Loads, LoadBytes()) case LoadCmdRpath: var RpathCmd := bytes.NewReader() if := binary.Read(, , &); != nil { return nil, } := new(Rpath) if .Path >= uint32(len()) { return nil, &FormatError{, "invalid path in rpath command", .Path} } .Path = cstring([.Path:]) .LoadBytes = LoadBytes() .Loads = append(.Loads, ) case LoadCmdDylib: var DylibCmd := bytes.NewReader() if := binary.Read(, , &); != nil { return nil, } := new(Dylib) if .Name >= uint32(len()) { return nil, &FormatError{, "invalid name in dynamic library command", .Name} } .Name = cstring([.Name:]) .Time = .Time .CurrentVersion = .CurrentVersion .CompatVersion = .CompatVersion .LoadBytes = LoadBytes() .Loads = append(.Loads, ) case LoadCmdSymtab: var SymtabCmd := bytes.NewReader() if := binary.Read(, , &); != nil { return nil, } , := saferio.ReadDataAt(, uint64(.Strsize), int64(.Stroff)) if != nil { return nil, } var int if .Magic == Magic64 { = 16 } else { = 12 } , := saferio.ReadDataAt(, uint64(.Nsyms)*uint64(), int64(.Symoff)) if != nil { return nil, } , := .parseSymtab(, , , &, ) if != nil { return nil, } .Loads = append(.Loads, ) .Symtab = case LoadCmdDysymtab: var DysymtabCmd := bytes.NewReader() if := binary.Read(, , &); != nil { return nil, } if .Symtab == nil { return nil, &FormatError{, "dynamic symbol table seen before any ordinary symbol table", nil} } else if .Iundefsym > uint32(len(.Symtab.Syms)) { return nil, &FormatError{, fmt.Sprintf( "undefined symbols index in dynamic symbol table command is greater than symbol table length (%d > %d)", .Iundefsym, len(.Symtab.Syms)), nil} } else if .Iundefsym+.Nundefsym > uint32(len(.Symtab.Syms)) { return nil, &FormatError{, fmt.Sprintf( "number of undefined symbols after index in dynamic symbol table command is greater than symbol table length (%d > %d)", .Iundefsym+.Nundefsym, len(.Symtab.Syms)), nil} } , := saferio.ReadDataAt(, uint64(.Nindirectsyms)*4, int64(.Indirectsymoff)) if != nil { return nil, } := make([]uint32, .Nindirectsyms) if := binary.Read(bytes.NewReader(), , ); != nil { return nil, } := new(Dysymtab) .LoadBytes = LoadBytes() .DysymtabCmd = .IndirectSyms = .Loads = append(.Loads, ) .Dysymtab = case LoadCmdSegment: var Segment32 := bytes.NewReader() if := binary.Read(, , &); != nil { return nil, } = new(Segment) .LoadBytes = .Cmd = .Len = .Name = cstring(.Name[0:]) .Addr = uint64(.Addr) .Memsz = uint64(.Memsz) .Offset = uint64(.Offset) .Filesz = uint64(.Filesz) .Maxprot = .Maxprot .Prot = .Prot .Nsect = .Nsect .Flag = .Flag .Loads = append(.Loads, ) for := 0; < int(.Nsect); ++ { var Section32 if := binary.Read(, , &); != nil { return nil, } := new(Section) .Name = cstring(.Name[0:]) .Seg = cstring(.Seg[0:]) .Addr = uint64(.Addr) .Size = uint64(.Size) .Offset = .Offset .Align = .Align .Reloff = .Reloff .Nreloc = .Nreloc .Flags = .Flags if := .pushSection(, ); != nil { return nil, } } case LoadCmdSegment64: var Segment64 := bytes.NewReader() if := binary.Read(, , &); != nil { return nil, } = new(Segment) .LoadBytes = .Cmd = .Len = .Name = cstring(.Name[0:]) .Addr = .Addr .Memsz = .Memsz .Offset = .Offset .Filesz = .Filesz .Maxprot = .Maxprot .Prot = .Prot .Nsect = .Nsect .Flag = .Flag .Loads = append(.Loads, ) for := 0; < int(.Nsect); ++ { var Section64 if := binary.Read(, , &); != nil { return nil, } := new(Section) .Name = cstring(.Name[0:]) .Seg = cstring(.Seg[0:]) .Addr = .Addr .Size = .Size .Offset = .Offset .Align = .Align .Reloff = .Reloff .Nreloc = .Nreloc .Flags = .Flags if := .pushSection(, ); != nil { return nil, } } } if != nil { if int64(.Offset) < 0 { return nil, &FormatError{, "invalid section offset", .Offset} } if int64(.Filesz) < 0 { return nil, &FormatError{, "invalid section file size", .Filesz} } .sr = io.NewSectionReader(, int64(.Offset), int64(.Filesz)) .ReaderAt = .sr } } return , nil } func ( *File) (, , []byte, *SymtabCmd, int64) (*Symtab, error) { := .ByteOrder := saferio.SliceCap[Symbol](uint64(.Nsyms)) if < 0 { return nil, &FormatError{, "too many symbols", nil} } := make([]Symbol, 0, ) := bytes.NewReader() for := 0; < int(.Nsyms); ++ { var Nlist64 if .Magic == Magic64 { if := binary.Read(, , &); != nil { return nil, } } else { var Nlist32 if := binary.Read(, , &); != nil { return nil, } .Name = .Name .Type = .Type .Sect = .Sect .Desc = .Desc .Value = uint64(.Value) } if .Name >= uint32(len()) { return nil, &FormatError{, "invalid name in symbol table", .Name} } // We add "_" to Go symbols. Strip it here. See issue 33808. := cstring([.Name:]) if strings.Contains(, ".") && [0] == '_' { = [1:] } = append(, Symbol{ Name: , Type: .Type, Sect: .Sect, Desc: .Desc, Value: .Value, }) } := new(Symtab) .LoadBytes = LoadBytes() .Syms = return , nil } type relocInfo struct { Addr uint32 Symnum uint32 } func ( *File) ( *Section, io.ReaderAt) error { .Sections = append(.Sections, ) .sr = io.NewSectionReader(, int64(.Offset), int64(.Size)) .ReaderAt = .sr if .Nreloc > 0 { , := saferio.ReadDataAt(, uint64(.Nreloc)*8, int64(.Reloff)) if != nil { return } := bytes.NewReader() := .ByteOrder .Relocs = make([]Reloc, .Nreloc) for := range .Relocs { := &.Relocs[] var relocInfo if := binary.Read(, , &); != nil { return } if .Addr&(1<<31) != 0 { // scattered .Addr = .Addr & (1<<24 - 1) .Type = uint8((.Addr >> 24) & (1<<4 - 1)) .Len = uint8((.Addr >> 28) & (1<<2 - 1)) .Pcrel = .Addr&(1<<30) != 0 .Value = .Symnum .Scattered = true } else { switch { case binary.LittleEndian: .Addr = .Addr .Value = .Symnum & (1<<24 - 1) .Pcrel = .Symnum&(1<<24) != 0 .Len = uint8((.Symnum >> 25) & (1<<2 - 1)) .Extern = .Symnum&(1<<27) != 0 .Type = uint8((.Symnum >> 28) & (1<<4 - 1)) case binary.BigEndian: .Addr = .Addr .Value = .Symnum >> 8 .Pcrel = .Symnum&(1<<7) != 0 .Len = uint8((.Symnum >> 5) & (1<<2 - 1)) .Extern = .Symnum&(1<<4) != 0 .Type = uint8(.Symnum & (1<<4 - 1)) default: panic("unreachable") } } } } return nil } func cstring( []byte) string { := bytes.IndexByte(, 0) if == -1 { = len() } return string([0:]) } // Segment returns the first Segment with the given name, or nil if no such segment exists. func ( *File) ( string) *Segment { for , := range .Loads { if , := .(*Segment); && .Name == { return } } return nil } // Section returns the first section with the given name, or nil if no such // section exists. func ( *File) ( string) *Section { for , := range .Sections { if .Name == { return } } return nil } // DWARF returns the DWARF debug information for the Mach-O file. func ( *File) () (*dwarf.Data, error) { := func( *Section) string { := .Name var int switch { case strings.HasPrefix(, "__debug_"): = 8 case strings.HasPrefix(, "__zdebug_"): = 9 default: return "" } // Mach-O executables truncate section names to 16 characters, mangling some DWARF sections. // As of DWARFv5 these are the only problematic section names (see DWARFv5 Appendix G). for , := range []string{ "__debug_str_offsets", "__zdebug_line_str", "__zdebug_loclists", "__zdebug_pubnames", "__zdebug_pubtypes", "__zdebug_rnglists", "__zdebug_str_offsets", } { if == [:16] { = break } } return [:] } := func( *Section) ([]byte, error) { , := .Data() if != nil && uint64(len()) < .Size { return nil, } if len() >= 12 && string([:4]) == "ZLIB" { := binary.BigEndian.Uint64([4:12]) := make([]byte, ) , := zlib.NewReader(bytes.NewBuffer([12:])) if != nil { return nil, } if , := io.ReadFull(, ); != nil { return nil, } if := .Close(); != nil { return nil, } = } return , nil } // There are many other DWARF sections, but these // are the ones the debug/dwarf package uses. // Don't bother loading others. var = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil} for , := range .Sections { := () if == "" { continue } if , := []; ! { continue } , := () if != nil { return nil, } [] = } , := dwarf.New(["abbrev"], nil, nil, ["info"], ["line"], nil, ["ranges"], ["str"]) if != nil { return nil, } // Look for DWARF4 .debug_types sections and DWARF5 sections. for , := range .Sections { := () if == "" { continue } if , := []; { // Already handled. continue } , := () if != nil { return nil, } if == "types" { = .AddTypes(fmt.Sprintf("types-%d", ), ) } else { = .AddSection(".debug_"+, ) } if != nil { return nil, } } return , nil } // ImportedSymbols returns the names of all symbols // referred to by the binary f that are expected to be // satisfied by other libraries at dynamic load time. func ( *File) () ([]string, error) { if .Dysymtab == nil || .Symtab == nil { return nil, &FormatError{0, "missing symbol table", nil} } := .Symtab := .Dysymtab var []string for , := range .Syms[.Iundefsym : .Iundefsym+.Nundefsym] { = append(, .Name) } return , nil } // ImportedLibraries returns the paths of all libraries // referred to by the binary f that are expected to be // linked with the binary at dynamic link time. func ( *File) () ([]string, error) { var []string for , := range .Loads { if , := .(*Dylib); { = append(, .Name) } } return , nil }