Skip to content

Instantly share code, notes, and snippets.

@felixbuenemann
Last active December 18, 2020 00:54
Show Gist options
  • Select an option

  • Save felixbuenemann/d08875fc3e1a69fe8d6eaa724829c950 to your computer and use it in GitHub Desktop.

Select an option

Save felixbuenemann/d08875fc3e1a69fe8d6eaa724829c950 to your computer and use it in GitHub Desktop.

Revisions

  1. felixbuenemann revised this gist Dec 2, 2020. 1 changed file with 7 additions and 15 deletions.
    22 changes: 7 additions & 15 deletions go.rb
    Original file line number Diff line number Diff line change
    @@ -61,22 +61,14 @@ def install
    next unless Hardware::CPU.arm?

    ENV["GOHOSTARCH"] = "#{Hardware::CPU.arch}"
    # The x86_64 bootstrap compiler can't build CGO enabled arm64 version.
    ENV["CGO_ENABLED"] = "0"
    # Build arm64 bootstrap compiler
    ENV["GOROOT_FINAL"] = buildpath/"gobootstrap"
    cd "src" do
    system "./make.bash", "--no-banner"
    end

    # Replace x86_64 bootstrap with cross compiled arm64 version.
    rm_rf "gobootstrap"
    (buildpath/"gobootstrap/bin").install "bin/go"
    (buildpath/"pkg/obj").rmtree
    (buildpath/"gobootstrap").install "pkg"
    (buildpath/"gobootstrap").install_symlink buildpath/"src"
    # Enable CGO now that we have an arm64 bootstrap compiler.
    ENV["CGO_ENABLED"] = "1"
    # Write CC wrapper with proper arch otherwise CGO build will fail.
    (buildpath/"cc_target").write <<~EOS
    #!/bin/sh
    exec "#{ENV["CC"]}" -arch "#{Hardware::CPU.arch}" "$@"
    EOS
    chmod "+x", "cc_target"
    ENV["CC_FOR_TARGET"] = buildpath/"cc_target"
    end

    cd "src" do
  2. felixbuenemann revised this gist Dec 2, 2020. 1 changed file with 0 additions and 682 deletions.
    682 changes: 0 additions & 682 deletions go.rb
    Original file line number Diff line number Diff line change
    @@ -27,8 +27,6 @@ class Go < Formula
    # sha256 "7d39badbe6096ffd120c3e997ee16fd026d12c4037eb055d26290a39f2689582" => :high_sierra
    # end

    patch :DATA

    head do
    url "https://go.googlesource.com/go.git"

    @@ -128,683 +126,3 @@ def install
    system bin/"go", "build", "hello.go"
    end
    end

    __END__
    diff --git a/src/cmd/buildid/buildid.go b/src/cmd/buildid/buildid.go
    index 699d977950..8e02a7ae10 100644
    --- a/src/cmd/buildid/buildid.go
    +++ b/src/cmd/buildid/buildid.go
    @@ -62,7 +62,7 @@ func main() {
    return
    }

    - f, err = os.OpenFile(file, os.O_WRONLY, 0)
    + f, err = os.OpenFile(file, os.O_RDWR, 0)
    if err != nil {
    log.Fatal(err)
    }
    diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go
    index 37b3d45977..e7bedfb84e 100644
    --- a/src/cmd/dist/buildtool.go
    +++ b/src/cmd/dist/buildtool.go
    @@ -53,6 +53,7 @@ var bootstrapDirs = []string{
    "cmd/compile/internal/x86",
    "cmd/compile/internal/wasm",
    "cmd/internal/bio",
    + "cmd/internal/codesign",
    "cmd/internal/gcprog",
    "cmd/internal/dwarf",
    "cmd/internal/edit",
    diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go
    index a88544e1af..3c7be5a3e3 100644
    --- a/src/cmd/go/internal/work/buildid.go
    +++ b/src/cmd/go/internal/work/buildid.go
    @@ -647,7 +647,7 @@ func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error {
    }

    if rewrite {
    - w, err := os.OpenFile(target, os.O_WRONLY, 0)
    + w, err := os.OpenFile(target, os.O_RDWR, 0)
    if err != nil {
    return err
    }
    diff --git a/src/cmd/internal/buildid/rewrite.go b/src/cmd/internal/buildid/rewrite.go
    index 5be54552a6..0f005f19da 100644
    --- a/src/cmd/internal/buildid/rewrite.go
    +++ b/src/cmd/internal/buildid/rewrite.go
    @@ -6,7 +6,9 @@ package buildid

    import (
    "bytes"
    + "cmd/internal/codesign"
    "crypto/sha256"
    + "debug/macho"
    "fmt"
    "io"
    )
    @@ -26,6 +28,11 @@ func FindAndHash(r io.Reader, id string, bufSize int) (matches []int64, hash [32
    zeros := make([]byte, len(id))
    idBytes := []byte(id)

    + // For Mach-O files, we want to exclude the code signature.
    + // The code signature contains hashes of the whole file (except the signature
    + // itself), including the buildid. So the buildid cannot contain the signature.
    + r = exculdeMachoCodeSignature(r)
    +
    // The strategy is to read the file through buf, looking for id,
    // but we need to worry about what happens if id is broken up
    // and returned in parts by two different reads.
    @@ -87,5 +94,42 @@ func Rewrite(w io.WriterAt, pos []int64, id string) error {
    return err
    }
    }
    +
    + // Update Mach-O code signature, if any.
    + if f, cmd, ok := findMachoCodeSignature(w); ok {
    + if codesign.Size(int64(cmd.Dataoff), "a.out") > int64(cmd.Datasize) {
    + // Existing code signature too small. Maybe it is signed with a
    + // different tool. Ignore.
    + return nil
    + }
    + text := f.Segment("__TEXT")
    + cs := make([]byte, cmd.Datasize)
    + codesign.Sign(cs, w.(io.Reader), "a.out", int64(cmd.Dataoff), int64(text.Offset), int64(text.Filesz), f.Type == macho.TypeExec)
    + if _, err := w.WriteAt(cs, int64(cmd.Dataoff)); err != nil {
    + return err
    + }
    + }
    +
    return nil
    }
    +
    +func exculdeMachoCodeSignature(r io.Reader) io.Reader {
    + _, cmd, ok := findMachoCodeSignature(r)
    + if !ok {
    + return r
    + }
    + return io.LimitReader(r, int64(cmd.Dataoff))
    +}
    +
    +func findMachoCodeSignature(r interface{}) (*macho.File, codesign.CodeSigCmd, bool) {
    + ra, ok := r.(io.ReaderAt)
    + if !ok {
    + return nil, codesign.CodeSigCmd{}, false
    + }
    + f, err := macho.NewFile(ra)
    + if err != nil {
    + return nil, codesign.CodeSigCmd{}, false
    + }
    + cmd, ok := codesign.FindCodeSigCmd(f)
    + return f, cmd, ok
    +}
    diff --git a/src/cmd/internal/codesign/codesign.go b/src/cmd/internal/codesign/codesign.go
    new file mode 100644
    index 0000000000..6e206b74f8
    --- /dev/null
    +++ b/src/cmd/internal/codesign/codesign.go
    @@ -0,0 +1,239 @@
    +// Copyright 2020 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 codesign provides basic functionalities for
    +// ad-hoc code signing of Mach-O files.
    +package codesign
    +
    +import (
    + "crypto/sha256"
    + "debug/macho"
    + "encoding/binary"
    + "io"
    + "unsafe"
    +)
    +
    +const (
    + pageSizeBits = 12
    + pageSize = 1 << pageSizeBits
    +)
    +
    +const LC_CODE_SIGNATURE = 0x1d
    +
    +const (
    + CSMAGIC_REQUIREMENT = 0xfade0c00 // single Requirement blob
    + CSMAGIC_REQUIREMENTS = 0xfade0c01 // Requirements vector (internal requirements)
    + CSMAGIC_CODEDIRECTORY = 0xfade0c02 // CodeDirectory blob
    + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 // embedded form of signature data
    + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1 // multi-arch collection of embedded signatures
    +
    + CSSLOT_CODEDIRECTORY = 0 // slot index for CodeDirectory
    +)
    +
    +const (
    + kSecCodeSignatureNoHash = 0 // null value
    + kSecCodeSignatureHashSHA1 = 1 // SHA-1
    + kSecCodeSignatureHashSHA256 = 2 // SHA-256
    + kSecCodeSignatureHashSHA256Truncated = 3 // SHA-256 truncated to first 20 bytes
    + kSecCodeSignatureHashSHA384 = 4 // SHA-384
    + kSecCodeSignatureHashSHA512 = 5 // SHA-512
    +)
    +
    +const (
    + CS_EXECSEG_MAIN_BINARY = 0x1 // executable segment denotes main binary
    + CS_EXECSEG_ALLOW_UNSIGNED = 0x10 // allow unsigned pages (for debugging)
    + CS_EXECSEG_DEBUGGER = 0x20 // main binary is debugger
    + CS_EXECSEG_JIT = 0x40 // JIT enabled
    + CS_EXECSEG_SKIP_LV = 0x80 // skip library validation
    + CS_EXECSEG_CAN_LOAD_CDHASH = 0x100 // can bless cdhash for execution
    + CS_EXECSEG_CAN_EXEC_CDHASH = 0x200 // can execute blessed cdhash
    +)
    +
    +type Blob struct {
    + typ uint32 // type of entry
    + offset uint32 // offset of entry
    + // data follows
    +}
    +
    +func (b *Blob) put(out []byte) []byte {
    + out = put32be(out, b.typ)
    + out = put32be(out, b.offset)
    + return out
    +}
    +
    +type SuperBlob struct {
    + magic uint32 // magic number
    + length uint32 // total length of SuperBlob
    + count uint32 // number of index entries following
    + // blobs []Blob
    +}
    +
    +func (s *SuperBlob) put(out []byte) []byte {
    + out = put32be(out, s.magic)
    + out = put32be(out, s.length)
    + out = put32be(out, s.count)
    + return out
    +}
    +
    +type CodeDirectory struct {
    + magic uint32 // magic number (CSMAGIC_CODEDIRECTORY)
    + length uint32 // total length of CodeDirectory blob
    + version uint32 // compatibility version
    + flags uint32 // setup and mode flags
    + hashOffset uint32 // offset of hash slot element at index zero
    + identOffset uint32 // offset of identifier string
    + nSpecialSlots uint32 // number of special hash slots
    + nCodeSlots uint32 // number of ordinary (code) hash slots
    + codeLimit uint32 // limit to main image signature range
    + hashSize uint8 // size of each hash in bytes
    + hashType uint8 // type of hash (cdHashType* constants)
    + _pad1 uint8 // unused (must be zero)
    + pageSize uint8 // log2(page size in bytes); 0 => infinite
    + _pad2 uint32 // unused (must be zero)
    + scatterOffset uint32
    + teamOffset uint32
    + _pad3 uint32
    + codeLimit64 uint64
    + execSegBase uint64
    + execSegLimit uint64
    + execSegFlags uint64
    + // data follows
    +}
    +
    +func (c *CodeDirectory) put(out []byte) []byte {
    + out = put32be(out, c.magic)
    + out = put32be(out, c.length)
    + out = put32be(out, c.version)
    + out = put32be(out, c.flags)
    + out = put32be(out, c.hashOffset)
    + out = put32be(out, c.identOffset)
    + out = put32be(out, c.nSpecialSlots)
    + out = put32be(out, c.nCodeSlots)
    + out = put32be(out, c.codeLimit)
    + out = put8(out, c.hashSize)
    + out = put8(out, c.hashType)
    + out = put8(out, c._pad1)
    + out = put8(out, c.pageSize)
    + out = put32be(out, c._pad2)
    + out = put32be(out, c.scatterOffset)
    + out = put32be(out, c.teamOffset)
    + out = put32be(out, c._pad3)
    + out = put64be(out, c.codeLimit64)
    + out = put64be(out, c.execSegBase)
    + out = put64be(out, c.execSegLimit)
    + out = put64be(out, c.execSegFlags)
    + return out
    +}
    +
    +// CodeSigCmd is Mach-O LC_CODE_SIGNATURE load command.
    +type CodeSigCmd struct {
    + Cmd uint32 // LC_CODE_SIGNATURE
    + Cmdsize uint32 // sizeof this command (16)
    + Dataoff uint32 // file offset of data in __LINKEDIT segment
    + Datasize uint32 // file size of data in __LINKEDIT segment
    +}
    +
    +func FindCodeSigCmd(f *macho.File) (CodeSigCmd, bool) {
    + get32 := f.ByteOrder.Uint32
    + for _, l := range f.Loads {
    + data := l.Raw()
    + cmd := get32(data)
    + if cmd == LC_CODE_SIGNATURE {
    + return CodeSigCmd{
    + cmd,
    + get32(data[4:]),
    + get32(data[8:]),
    + get32(data[12:]),
    + }, true
    + }
    + }
    + return CodeSigCmd{}, false
    +}
    +
    +func get32le(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
    +func put32le(b []byte, x uint32) []byte { binary.LittleEndian.PutUint32(b, x); return b[4:] }
    +func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
    +func put64le(b []byte, x uint64) []byte { binary.LittleEndian.PutUint64(b, x); return b[8:] }
    +func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] }
    +func put8(b []byte, x uint8) []byte { b[0] = x; return b[1:] }
    +func puts(b, s []byte) []byte { n := copy(b, s); return b[n:] }
    +
    +// round x up to a multiple of n. n must be a power of 2.
    +func roundUp(x, n int64) int64 { return (x + n - 1) &^ (n - 1) }
    +
    +// Size computes the size of the code signature.
    +func Size(codeSize int64, id string) int64 {
    + nhashes := (codeSize + pageSize - 1) / pageSize
    + idOff := int64(unsafe.Sizeof(CodeDirectory{}))
    + hashOff := idOff + int64(len(id)+1)
    + cdirSz := hashOff + nhashes*sha256.Size
    + return int64(unsafe.Sizeof(SuperBlob{})+unsafe.Sizeof(Blob{})) + cdirSz
    +}
    +
    +// Sign generates an ad-hoc code signature and writes it to out.
    +// data is the file content without the signature, of size codeSize.
    +// textOff and textSize is the file offset and size of the text segment.
    +// isMain is true if thie is a main executable.
    +func Sign(out []byte, data io.Reader, id string, codeSize, textOff, textSize int64, isMain bool) {
    + nhashes := (codeSize + pageSize - 1) / pageSize
    + idOff := int64(unsafe.Sizeof(CodeDirectory{}))
    + hashOff := idOff + int64(len(id)+1)
    + sz := Size(codeSize, id)
    +
    + // emit blob headers
    + sb := SuperBlob{
    + magic: CSMAGIC_EMBEDDED_SIGNATURE,
    + length: uint32(sz),
    + count: 1,
    + }
    + blob := Blob{
    + typ: CSSLOT_CODEDIRECTORY,
    + offset: uint32(unsafe.Sizeof(SuperBlob{}) + unsafe.Sizeof(Blob{})),
    + }
    + cdir := CodeDirectory{
    + magic: CSMAGIC_CODEDIRECTORY,
    + length: uint32(sz) - uint32(unsafe.Sizeof(SuperBlob{})+unsafe.Sizeof(Blob{})),
    + version: 0x20400,
    + flags: 0x20002, // adhoc | linkerSigned
    + hashOffset: uint32(hashOff),
    + identOffset: uint32(idOff),
    + nCodeSlots: uint32(nhashes),
    + codeLimit: uint32(codeSize),
    + hashSize: sha256.Size,
    + hashType: kSecCodeSignatureHashSHA256,
    + pageSize: uint8(pageSizeBits),
    + execSegBase: uint64(textOff),
    + execSegLimit: uint64(textSize),
    + }
    + if isMain {
    + cdir.execSegFlags = CS_EXECSEG_MAIN_BINARY
    + }
    +
    + outp := out
    + outp = sb.put(outp)
    + outp = blob.put(outp)
    + outp = cdir.put(outp)
    + outp = puts(outp, []byte(id+"\000"))
    +
    + // emit hashes
    + var buf [pageSize]byte
    + p := 0
    + for p < int(codeSize) {
    + n, err := io.ReadFull(data, buf[:])
    + if err == io.EOF {
    + break
    + }
    + if err != nil && err != io.ErrUnexpectedEOF {
    + panic(err)
    + }
    + if p+n > int(codeSize) {
    + n = int(codeSize) - p
    + }
    + p += n
    + h := sha256.New()
    + h.Write(buf[:n])
    + b := h.Sum(nil)
    + outp = puts(outp, b[:])
    + }
    +}
    diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
    index 51abefc887..d439754477 100644
    --- a/src/cmd/link/internal/ld/macho.go
    +++ b/src/cmd/link/internal/ld/macho.go
    @@ -6,6 +6,7 @@ package ld

    import (
    "bytes"
    + "cmd/internal/codesign"
    "cmd/internal/objabi"
    "cmd/internal/sys"
    "cmd/link/internal/loader"
    @@ -643,6 +644,8 @@ func asmbMacho(ctxt *Link) {
    }
    ctxt.Out.SeekSet(0)

    + ldr := ctxt.loader
    +
    /* apple MACH */
    va := *FlagTextAddr - int64(HEADR)

    @@ -757,9 +760,8 @@ func asmbMacho(ctxt *Link) {
    }
    }

    + var codesigOff int64
    if !*FlagD {
    - ldr := ctxt.loader
    -
    // must match domacholink below
    s1 := ldr.SymSize(ldr.Lookup(".machorebase", 0))
    s2 := ldr.SymSize(ldr.Lookup(".machobind", 0))
    @@ -767,15 +769,18 @@ func asmbMacho(ctxt *Link) {
    s4 := ldr.SymSize(ctxt.ArchSyms.LinkEditPLT)
    s5 := ldr.SymSize(ctxt.ArchSyms.LinkEditGOT)
    s6 := ldr.SymSize(ldr.Lookup(".machosymstr", 0))
    + s7 := ldr.SymSize(ldr.Lookup(".machocodesig", 0))

    if ctxt.LinkMode != LinkExternal {
    ms := newMachoSeg("__LINKEDIT", 0)
    ms.vaddr = uint64(Rnd(int64(Segdata.Vaddr+Segdata.Length), int64(*FlagRound)))
    - ms.vsize = uint64(s1 + s2 + s3 + s4 + s5 + s6)
    + ms.vsize = uint64(s1 + s2 + s3 + s4 + s5 + s6 + s7)
    ms.fileoffset = uint64(linkoff)
    ms.filesize = ms.vsize
    ms.prot1 = 1
    ms.prot2 = 1
    +
    + codesigOff = linkoff + s1 + s2 + s3 + s4 + s5 + s6
    }

    if ctxt.LinkMode != LinkExternal && ctxt.IsPIE() {
    @@ -814,12 +819,31 @@ func asmbMacho(ctxt *Link) {
    stringtouint32(ml.data[4:], lib)
    }
    }
    +
    + if ctxt.LinkMode != LinkExternal && objabi.GOOS == "darwin" && ctxt.IsARM64() {
    + ml := newMachoLoad(ctxt.Arch, LC_CODE_SIGNATURE, 2)
    + ml.data[0] = uint32(codesigOff)
    + ml.data[1] = uint32(s7)
    + }
    }

    a := machowrite(ctxt, ctxt.Arch, ctxt.Out, ctxt.LinkMode)
    if int32(a) > HEADR {
    Exitf("HEADR too small: %d > %d", a, HEADR)
    }
    +
    + // Now we have written everything. Compute the code signature (which
    + // is a hash of the file content, so it must be done at last.)
    + if ctxt.IsInternal() && objabi.GOOS == "darwin" && ctxt.IsARM64() {
    + cs := ldr.Lookup(".machocodesig", 0)
    + data := ctxt.Out.Data()
    + if int64(len(data)) != codesigOff {
    + panic("wrong size")
    + }
    + codesign.Sign(ldr.Data(cs), bytes.NewReader(data), "a.out", codesigOff, int64(Segtext.Fileoff), int64(Segtext.Filelen), ctxt.IsExe() || ctxt.IsPIE())
    + ctxt.Out.SeekSet(codesigOff)
    + ctxt.Out.Write(ldr.Data(cs))
    + }
    }

    func symkind(ldr *loader.Loader, s loader.Sym) int {
    @@ -1057,7 +1081,6 @@ func machodysymtab(ctxt *Link, base int64) {

    func doMachoLink(ctxt *Link) int64 {
    machosymtab(ctxt)
    -
    machoDyldInfo(ctxt)

    ldr := ctxt.loader
    @@ -1070,6 +1093,8 @@ func doMachoLink(ctxt *Link) int64 {
    s5 := ctxt.ArchSyms.LinkEditGOT
    s6 := ldr.Lookup(".machosymstr", 0)

    + size := ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4) + ldr.SymSize(s5) + ldr.SymSize(s6)
    +
    // Force the linkedit section to end on a 16-byte
    // boundary. This allows pure (non-cgo) Go binaries
    // to be code signed correctly.
    @@ -1087,13 +1112,14 @@ func doMachoLink(ctxt *Link) int64 {
    // boundary, codesign_allocate will not need to apply
    // any alignment padding itself, working around the
    // issue.
    - s6b := ldr.MakeSymbolUpdater(s6)
    - for s6b.Size()%16 != 0 {
    - s6b.AddUint8(0)
    + if size%16 != 0 {
    + n := 16 - size%16
    + s6b := ldr.MakeSymbolUpdater(s6)
    + s6b.Grow(s6b.Size() + n)
    + s6b.SetSize(s6b.Size() + n)
    + size += n
    }

    - size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4) + ldr.SymSize(s5) + ldr.SymSize(s6))
    -
    if size > 0 {
    linkoff = Rnd(int64(uint64(HEADR)+Segtext.Length), int64(*FlagRound)) + Rnd(int64(Segrelrodata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdwarf.Filelen), int64(*FlagRound))
    ctxt.Out.SeekSet(linkoff)
    @@ -1104,9 +1130,12 @@ func doMachoLink(ctxt *Link) int64 {
    ctxt.Out.Write(ldr.Data(s4))
    ctxt.Out.Write(ldr.Data(s5))
    ctxt.Out.Write(ldr.Data(s6))
    +
    + s7 := machoCodeSigSym(ctxt, linkoff+size)
    + size += ldr.SymSize(s7)
    }

    - return Rnd(int64(size), int64(*FlagRound))
    + return Rnd(size, int64(*FlagRound))
    }

    func machorelocsect(ctxt *Link, out *OutBuf, sect *sym.Section, syms []loader.Sym) {
    @@ -1378,3 +1407,18 @@ func machoDyldInfo(ctxt *Link) {
    // e.g. dlsym'd. But internal linking is not the default in that case, so
    // it is fine.
    }
    +
    +// machoCodeSigSym creates and returns a symbol for code signature.
    +// The symbol context is left as zeros, which will be generated at the end
    +// (as it depends on the rest of the file).
    +func machoCodeSigSym(ctxt *Link, codeSize int64) loader.Sym {
    + ldr := ctxt.loader
    + cs := ldr.CreateSymForUpdate(".machocodesig", 0)
    + if objabi.GOOS != "darwin" || !ctxt.IsARM64() || ctxt.IsExternal() {
    + return cs.Sym()
    + }
    + sz := codesign.Size(codeSize, "a.out")
    + cs.Grow(sz)
    + cs.SetSize(sz)
    + return cs.Sym()
    +}
    diff --git a/src/cmd/link/internal/ld/macho_combine_dwarf.go b/src/cmd/link/internal/ld/macho_combine_dwarf.go
    index 77ee8a4d62..fc57fe7d0f 100644
    --- a/src/cmd/link/internal/ld/macho_combine_dwarf.go
    +++ b/src/cmd/link/internal/ld/macho_combine_dwarf.go
    @@ -6,6 +6,7 @@ package ld

    import (
    "bytes"
    + "cmd/internal/codesign"
    "compress/zlib"
    "debug/macho"
    "encoding/binary"
    @@ -204,6 +205,8 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    return err
    }

    + var outlinkseg macho.Segment64
    + var outlinksegCmdOff, codeSigOff, codeSigSize int64
    reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
    for i := uint32(0); i < exem.Ncmd; i++ {
    cmd, err := reader.Next()
    @@ -213,7 +216,12 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    linkoffset := uint64(linkstart) - linkseg.Offset
    switch cmd.Cmd {
    case macho.LoadCmdSegment64:
    - err = machoUpdateSegment(reader, linkseg, linkoffset)
    + var seg macho.Segment64
    + err = machoUpdateSegment(reader, &seg, linkseg, linkoffset)
    + if seg.Name == [len(seg.Name)]byte{'_', '_', 'L', 'I', 'N', 'K', 'E', 'D', 'I', 'T'} {
    + outlinkseg = seg
    + outlinksegCmdOff = reader.offset
    + }
    case macho.LoadCmdSegment:
    panic("unexpected 32-bit segment")
    case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
    @@ -222,7 +230,18 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.SymtabCmd{}, "Symoff", "Stroff")
    case macho.LoadCmdDysymtab:
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff")
    - case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
    + case LC_CODE_SIGNATURE:
    + cmd := linkEditDataCmd{}
    + err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &cmd, "DataOff")
    + if err != nil {
    + return err
    + }
    + // We also need to update code signature's size.
    + codeSigOff = int64(cmd.DataOff)
    + codeSigSize = codesign.Size(codeSigOff, "a.out")
    + cmd.DataLen = uint32(codeSigSize)
    + err = reader.WriteAt(0, cmd)
    + case LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &linkEditDataCmd{}, "DataOff")
    case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &encryptionInfoCmd{}, "CryptOff")
    @@ -236,7 +255,33 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    }
    }
    // Do the final update of the DWARF segment's load command.
    - return machoUpdateDwarfHeader(&reader, compressedSects, dwarfsize, dwarfstart, realdwarf)
    + err = machoUpdateDwarfHeader(&reader, compressedSects, dwarfsize, dwarfstart, realdwarf)
    + if err != nil {
    + return err
    + }
    +
    + if codeSigOff != 0 {
    + // Code signature's size changed. Fix up __LINKEDIT segment's size.
    + if outlinksegCmdOff == 0 {
    + panic("__LINKEDIT segment not found")
    + }
    + reader.offset = outlinksegCmdOff
    + outlinkseg.Filesz = uint64(codeSigOff+codeSigSize) - outlinkseg.Offset
    + outlinkseg.Memsz = outlinkseg.Filesz
    + reader.WriteAt(0, outlinkseg)
    +
    + // Update code signature, if any. This needs to be done at last, as
    + // the signature depends on the rest of the file.
    + text := exem.Segment("__TEXT")
    + cs := make([]byte, codeSigSize)
    + _, err = outf.Seek(0, os.SEEK_SET)
    + if err != nil {
    + return err
    + }
    + codesign.Sign(cs, outf, "a.out", codeSigOff, int64(text.Offset), int64(text.Filesz), ctxt.IsExe() || ctxt.IsPIE())
    + _, err = outf.WriteAt(cs, codeSigOff)
    + }
    + return err
    }

    // machoCompressSections tries to compress the DWARF segments in dwarfm,
    @@ -309,9 +354,8 @@ func machoCompressSection(sectBytes []byte) (compressed bool, contents []byte, e

    // machoUpdateSegment updates the load command for a moved segment.
    // Only the linkedit segment should move, and it should have 0 sections.
    -func machoUpdateSegment(r loadCmdReader, linkseg *macho.Segment, linkoffset uint64) error {
    - var seg macho.Segment64
    - if err := r.ReadAt(0, &seg); err != nil {
    +func machoUpdateSegment(r loadCmdReader, seg *macho.Segment64, linkseg *macho.Segment, linkoffset uint64) error {
    + if err := r.ReadAt(0, seg); err != nil {
    return err
    }

    @@ -320,11 +364,11 @@ func machoUpdateSegment(r loadCmdReader, linkseg *macho.Segment, linkoffset uint
    return nil
    }
    seg.Offset += linkoffset
    - if err := r.WriteAt(0, &seg); err != nil {
    + if err := r.WriteAt(0, seg); err != nil {
    return err
    }
    // There shouldn't be any sections, but just to make sure...
    - return machoUpdateSections(r, &seg, linkoffset, nil)
    + return machoUpdateSections(r, seg, linkoffset, nil)
    }

    func machoUpdateSections(r loadCmdReader, seg *macho.Segment64, deltaOffset uint64, compressedSects []*macho.Section) error {
    diff --git a/src/cmd/link/internal/ld/outbuf.go b/src/cmd/link/internal/ld/outbuf.go
    index 36ec394077..dda7d83f18 100644
    --- a/src/cmd/link/internal/ld/outbuf.go
    +++ b/src/cmd/link/internal/ld/outbuf.go
    @@ -113,6 +113,7 @@ func (out *OutBuf) Close() error {
    }
    if out.isMmapped() {
    out.copyHeap()
    + out.purgeKernelCache()
    out.munmap()
    }
    if out.f == nil {
    @@ -135,6 +136,15 @@ func (out *OutBuf) isMmapped() bool {
    return len(out.buf) != 0
    }

    +// Data returns the whole written OutBuf as a byte slice.
    +func (out *OutBuf) Data() []byte {
    + if out.isMmapped() {
    + out.copyHeap()
    + return out.buf
    + }
    + return out.heap
    +}
    +
    // copyHeap copies the heap to the mmapped section of memory, returning true if
    // a copy takes place.
    func (out *OutBuf) copyHeap() bool {
    diff --git a/src/cmd/link/internal/ld/outbuf_darwin.go b/src/cmd/link/internal/ld/outbuf_darwin.go
    index d7e3372230..402543363d 100644
    --- a/src/cmd/link/internal/ld/outbuf_darwin.go
    +++ b/src/cmd/link/internal/ld/outbuf_darwin.go
    @@ -36,3 +36,8 @@ func (out *OutBuf) fallocate(size uint64) error {

    return nil
    }
    +
    +func (out *OutBuf) purgeKernelCache() {
    + syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(&out.buf[0])), uintptr(len(out.buf)), syscall.MS_INVALIDATE)
    + // Best effort. Ignore error.
    +}
    diff --git a/src/cmd/link/internal/ld/outbuf_notdarwin.go b/src/cmd/link/internal/ld/outbuf_notdarwin.go
    new file mode 100644
    index 0000000000..f7cac4ca0f
    --- /dev/null
    +++ b/src/cmd/link/internal/ld/outbuf_notdarwin.go
    @@ -0,0 +1,9 @@
    +// Copyright 2020 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.
    +
    +// +build !darwin
    +
    +package ld
    +
    +func (out *OutBuf) purgeKernelCache() {}
  3. felixbuenemann created this gist Dec 2, 2020.
    810 changes: 810 additions & 0 deletions go.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,810 @@
    class Go < Formula
    desc "Open source programming language to build simple/reliable/efficient software"
    homepage "https://golang.org"
    license "BSD-3-Clause"

    # stable do
    # url "https://golang.org/dl/go1.15.5.src.tar.gz"
    # mirror "https://fossies.org/linux/misc/go1.15.5.src.tar.gz"
    # sha256 "c1076b90cf94b73ebed62a81d802cd84d43d02dea8c07abdc922c57a071c84f1"
    #
    # go_version = version.major_minor
    # resource "gotools" do
    # url "https://go.googlesource.com/tools.git",
    # branch: "release-branch.go#{go_version}"
    # end
    # end

    livecheck do
    url "https://golang.org/dl/"
    regex(/href=.*?go[._-]?v?(\d+(?:\.\d+)+)[._-]src\.t/i)
    end

    # bottle do
    # sha256 "61f04e266ba1f69573ae46766f8988029c5a34f4ae4f1ac0951cf3daf4d919d1" => :big_sur
    # sha256 "77f172eb6849a0f86eed4fa8eaeb9e8c69223b7f154a86505a7507b4ea78de79" => :catalina
    # sha256 "70969a6147bad6960136bdea78063d39b527c993a725ad525a3cf6e9f8ad6fa5" => :mojave
    # sha256 "7d39badbe6096ffd120c3e997ee16fd026d12c4037eb055d26290a39f2689582" => :high_sierra
    # end

    patch :DATA

    head do
    url "https://go.googlesource.com/go.git"

    resource "gotools" do
    url "https://go.googlesource.com/tools.git"
    end
    end

    # Don't update this unless this version cannot bootstrap the new version.
    resource "gobootstrap" do
    on_macos do
    url "https://storage.googleapis.com/golang/go1.15.5.darwin-amd64.tar.gz"
    sha256 "359a4334b8c8f5e3067e5a76f16419791ac3fef4613d8e8e1eac0b9719915f6d"
    end

    on_linux do
    url "https://storage.googleapis.com/golang/go1.15.5.linux-amd64.tar.gz"
    sha256 "9a58494e8da722c3aef248c9227b0e9c528c7318309827780f16220998180a0d"
    end
    end

    def install
    (buildpath/"gobootstrap").install resource("gobootstrap")
    ENV["GOROOT_BOOTSTRAP"] = buildpath/"gobootstrap"

    on_macos do
    # Work around bug in Rosetta 2, see:
    # https://github.com/golang/go/issues/42700
    ENV["GODEBUG"] = "asyncpreemptoff=1" if Hardware::CPU.physical_cpu_arm64?

    # Skip building a bootstrap compiler unless we build for arm64.
    next unless Hardware::CPU.arm?

    ENV["GOHOSTARCH"] = "#{Hardware::CPU.arch}"
    # The x86_64 bootstrap compiler can't build CGO enabled arm64 version.
    ENV["CGO_ENABLED"] = "0"
    # Build arm64 bootstrap compiler
    ENV["GOROOT_FINAL"] = buildpath/"gobootstrap"
    cd "src" do
    system "./make.bash", "--no-banner"
    end

    # Replace x86_64 bootstrap with cross compiled arm64 version.
    rm_rf "gobootstrap"
    (buildpath/"gobootstrap/bin").install "bin/go"
    (buildpath/"pkg/obj").rmtree
    (buildpath/"gobootstrap").install "pkg"
    (buildpath/"gobootstrap").install_symlink buildpath/"src"
    # Enable CGO now that we have an arm64 bootstrap compiler.
    ENV["CGO_ENABLED"] = "1"
    end

    cd "src" do
    ENV["GOROOT_FINAL"] = libexec
    system "./make.bash", "--no-clean"
    end

    (buildpath/"pkg/obj").rmtree
    rm_rf "gobootstrap" # Bootstrap not required beyond compile.
    libexec.install Dir["*"]
    bin.install_symlink Dir[libexec/"bin/go*"]

    system bin/"go", "install", "-race", "std"

    # Build and install godoc
    ENV.prepend_path "PATH", bin
    ENV["GOPATH"] = buildpath
    (buildpath/"src/golang.org/x/tools").install resource("gotools")
    cd "src/golang.org/x/tools/cmd/godoc/" do
    system "go", "build"
    (libexec/"bin").install "godoc"
    end
    bin.install_symlink libexec/"bin/godoc"
    end

    test do
    (testpath/"hello.go").write <<~EOS
    package main
    import "fmt"
    func main() {
    fmt.Println("Hello World")
    }
    EOS
    # Run go fmt check for no errors then run the program.
    # This is a a bare minimum of go working as it uses fmt, build, and run.
    system bin/"go", "fmt", "hello.go"
    assert_equal "Hello World\n", shell_output("#{bin}/go run hello.go")

    # godoc was installed
    assert_predicate libexec/"bin/godoc", :exist?
    assert_predicate libexec/"bin/godoc", :executable?

    ENV["GOOS"] = "freebsd"
    ENV["GOARCH"] = "amd64"
    system bin/"go", "build", "hello.go"
    end
    end

    __END__
    diff --git a/src/cmd/buildid/buildid.go b/src/cmd/buildid/buildid.go
    index 699d977950..8e02a7ae10 100644
    --- a/src/cmd/buildid/buildid.go
    +++ b/src/cmd/buildid/buildid.go
    @@ -62,7 +62,7 @@ func main() {
    return
    }

    - f, err = os.OpenFile(file, os.O_WRONLY, 0)
    + f, err = os.OpenFile(file, os.O_RDWR, 0)
    if err != nil {
    log.Fatal(err)
    }
    diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go
    index 37b3d45977..e7bedfb84e 100644
    --- a/src/cmd/dist/buildtool.go
    +++ b/src/cmd/dist/buildtool.go
    @@ -53,6 +53,7 @@ var bootstrapDirs = []string{
    "cmd/compile/internal/x86",
    "cmd/compile/internal/wasm",
    "cmd/internal/bio",
    + "cmd/internal/codesign",
    "cmd/internal/gcprog",
    "cmd/internal/dwarf",
    "cmd/internal/edit",
    diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go
    index a88544e1af..3c7be5a3e3 100644
    --- a/src/cmd/go/internal/work/buildid.go
    +++ b/src/cmd/go/internal/work/buildid.go
    @@ -647,7 +647,7 @@ func (b *Builder) updateBuildID(a *Action, target string, rewrite bool) error {
    }

    if rewrite {
    - w, err := os.OpenFile(target, os.O_WRONLY, 0)
    + w, err := os.OpenFile(target, os.O_RDWR, 0)
    if err != nil {
    return err
    }
    diff --git a/src/cmd/internal/buildid/rewrite.go b/src/cmd/internal/buildid/rewrite.go
    index 5be54552a6..0f005f19da 100644
    --- a/src/cmd/internal/buildid/rewrite.go
    +++ b/src/cmd/internal/buildid/rewrite.go
    @@ -6,7 +6,9 @@ package buildid

    import (
    "bytes"
    + "cmd/internal/codesign"
    "crypto/sha256"
    + "debug/macho"
    "fmt"
    "io"
    )
    @@ -26,6 +28,11 @@ func FindAndHash(r io.Reader, id string, bufSize int) (matches []int64, hash [32
    zeros := make([]byte, len(id))
    idBytes := []byte(id)

    + // For Mach-O files, we want to exclude the code signature.
    + // The code signature contains hashes of the whole file (except the signature
    + // itself), including the buildid. So the buildid cannot contain the signature.
    + r = exculdeMachoCodeSignature(r)
    +
    // The strategy is to read the file through buf, looking for id,
    // but we need to worry about what happens if id is broken up
    // and returned in parts by two different reads.
    @@ -87,5 +94,42 @@ func Rewrite(w io.WriterAt, pos []int64, id string) error {
    return err
    }
    }
    +
    + // Update Mach-O code signature, if any.
    + if f, cmd, ok := findMachoCodeSignature(w); ok {
    + if codesign.Size(int64(cmd.Dataoff), "a.out") > int64(cmd.Datasize) {
    + // Existing code signature too small. Maybe it is signed with a
    + // different tool. Ignore.
    + return nil
    + }
    + text := f.Segment("__TEXT")
    + cs := make([]byte, cmd.Datasize)
    + codesign.Sign(cs, w.(io.Reader), "a.out", int64(cmd.Dataoff), int64(text.Offset), int64(text.Filesz), f.Type == macho.TypeExec)
    + if _, err := w.WriteAt(cs, int64(cmd.Dataoff)); err != nil {
    + return err
    + }
    + }
    +
    return nil
    }
    +
    +func exculdeMachoCodeSignature(r io.Reader) io.Reader {
    + _, cmd, ok := findMachoCodeSignature(r)
    + if !ok {
    + return r
    + }
    + return io.LimitReader(r, int64(cmd.Dataoff))
    +}
    +
    +func findMachoCodeSignature(r interface{}) (*macho.File, codesign.CodeSigCmd, bool) {
    + ra, ok := r.(io.ReaderAt)
    + if !ok {
    + return nil, codesign.CodeSigCmd{}, false
    + }
    + f, err := macho.NewFile(ra)
    + if err != nil {
    + return nil, codesign.CodeSigCmd{}, false
    + }
    + cmd, ok := codesign.FindCodeSigCmd(f)
    + return f, cmd, ok
    +}
    diff --git a/src/cmd/internal/codesign/codesign.go b/src/cmd/internal/codesign/codesign.go
    new file mode 100644
    index 0000000000..6e206b74f8
    --- /dev/null
    +++ b/src/cmd/internal/codesign/codesign.go
    @@ -0,0 +1,239 @@
    +// Copyright 2020 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 codesign provides basic functionalities for
    +// ad-hoc code signing of Mach-O files.
    +package codesign
    +
    +import (
    + "crypto/sha256"
    + "debug/macho"
    + "encoding/binary"
    + "io"
    + "unsafe"
    +)
    +
    +const (
    + pageSizeBits = 12
    + pageSize = 1 << pageSizeBits
    +)
    +
    +const LC_CODE_SIGNATURE = 0x1d
    +
    +const (
    + CSMAGIC_REQUIREMENT = 0xfade0c00 // single Requirement blob
    + CSMAGIC_REQUIREMENTS = 0xfade0c01 // Requirements vector (internal requirements)
    + CSMAGIC_CODEDIRECTORY = 0xfade0c02 // CodeDirectory blob
    + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 // embedded form of signature data
    + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1 // multi-arch collection of embedded signatures
    +
    + CSSLOT_CODEDIRECTORY = 0 // slot index for CodeDirectory
    +)
    +
    +const (
    + kSecCodeSignatureNoHash = 0 // null value
    + kSecCodeSignatureHashSHA1 = 1 // SHA-1
    + kSecCodeSignatureHashSHA256 = 2 // SHA-256
    + kSecCodeSignatureHashSHA256Truncated = 3 // SHA-256 truncated to first 20 bytes
    + kSecCodeSignatureHashSHA384 = 4 // SHA-384
    + kSecCodeSignatureHashSHA512 = 5 // SHA-512
    +)
    +
    +const (
    + CS_EXECSEG_MAIN_BINARY = 0x1 // executable segment denotes main binary
    + CS_EXECSEG_ALLOW_UNSIGNED = 0x10 // allow unsigned pages (for debugging)
    + CS_EXECSEG_DEBUGGER = 0x20 // main binary is debugger
    + CS_EXECSEG_JIT = 0x40 // JIT enabled
    + CS_EXECSEG_SKIP_LV = 0x80 // skip library validation
    + CS_EXECSEG_CAN_LOAD_CDHASH = 0x100 // can bless cdhash for execution
    + CS_EXECSEG_CAN_EXEC_CDHASH = 0x200 // can execute blessed cdhash
    +)
    +
    +type Blob struct {
    + typ uint32 // type of entry
    + offset uint32 // offset of entry
    + // data follows
    +}
    +
    +func (b *Blob) put(out []byte) []byte {
    + out = put32be(out, b.typ)
    + out = put32be(out, b.offset)
    + return out
    +}
    +
    +type SuperBlob struct {
    + magic uint32 // magic number
    + length uint32 // total length of SuperBlob
    + count uint32 // number of index entries following
    + // blobs []Blob
    +}
    +
    +func (s *SuperBlob) put(out []byte) []byte {
    + out = put32be(out, s.magic)
    + out = put32be(out, s.length)
    + out = put32be(out, s.count)
    + return out
    +}
    +
    +type CodeDirectory struct {
    + magic uint32 // magic number (CSMAGIC_CODEDIRECTORY)
    + length uint32 // total length of CodeDirectory blob
    + version uint32 // compatibility version
    + flags uint32 // setup and mode flags
    + hashOffset uint32 // offset of hash slot element at index zero
    + identOffset uint32 // offset of identifier string
    + nSpecialSlots uint32 // number of special hash slots
    + nCodeSlots uint32 // number of ordinary (code) hash slots
    + codeLimit uint32 // limit to main image signature range
    + hashSize uint8 // size of each hash in bytes
    + hashType uint8 // type of hash (cdHashType* constants)
    + _pad1 uint8 // unused (must be zero)
    + pageSize uint8 // log2(page size in bytes); 0 => infinite
    + _pad2 uint32 // unused (must be zero)
    + scatterOffset uint32
    + teamOffset uint32
    + _pad3 uint32
    + codeLimit64 uint64
    + execSegBase uint64
    + execSegLimit uint64
    + execSegFlags uint64
    + // data follows
    +}
    +
    +func (c *CodeDirectory) put(out []byte) []byte {
    + out = put32be(out, c.magic)
    + out = put32be(out, c.length)
    + out = put32be(out, c.version)
    + out = put32be(out, c.flags)
    + out = put32be(out, c.hashOffset)
    + out = put32be(out, c.identOffset)
    + out = put32be(out, c.nSpecialSlots)
    + out = put32be(out, c.nCodeSlots)
    + out = put32be(out, c.codeLimit)
    + out = put8(out, c.hashSize)
    + out = put8(out, c.hashType)
    + out = put8(out, c._pad1)
    + out = put8(out, c.pageSize)
    + out = put32be(out, c._pad2)
    + out = put32be(out, c.scatterOffset)
    + out = put32be(out, c.teamOffset)
    + out = put32be(out, c._pad3)
    + out = put64be(out, c.codeLimit64)
    + out = put64be(out, c.execSegBase)
    + out = put64be(out, c.execSegLimit)
    + out = put64be(out, c.execSegFlags)
    + return out
    +}
    +
    +// CodeSigCmd is Mach-O LC_CODE_SIGNATURE load command.
    +type CodeSigCmd struct {
    + Cmd uint32 // LC_CODE_SIGNATURE
    + Cmdsize uint32 // sizeof this command (16)
    + Dataoff uint32 // file offset of data in __LINKEDIT segment
    + Datasize uint32 // file size of data in __LINKEDIT segment
    +}
    +
    +func FindCodeSigCmd(f *macho.File) (CodeSigCmd, bool) {
    + get32 := f.ByteOrder.Uint32
    + for _, l := range f.Loads {
    + data := l.Raw()
    + cmd := get32(data)
    + if cmd == LC_CODE_SIGNATURE {
    + return CodeSigCmd{
    + cmd,
    + get32(data[4:]),
    + get32(data[8:]),
    + get32(data[12:]),
    + }, true
    + }
    + }
    + return CodeSigCmd{}, false
    +}
    +
    +func get32le(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
    +func put32le(b []byte, x uint32) []byte { binary.LittleEndian.PutUint32(b, x); return b[4:] }
    +func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
    +func put64le(b []byte, x uint64) []byte { binary.LittleEndian.PutUint64(b, x); return b[8:] }
    +func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] }
    +func put8(b []byte, x uint8) []byte { b[0] = x; return b[1:] }
    +func puts(b, s []byte) []byte { n := copy(b, s); return b[n:] }
    +
    +// round x up to a multiple of n. n must be a power of 2.
    +func roundUp(x, n int64) int64 { return (x + n - 1) &^ (n - 1) }
    +
    +// Size computes the size of the code signature.
    +func Size(codeSize int64, id string) int64 {
    + nhashes := (codeSize + pageSize - 1) / pageSize
    + idOff := int64(unsafe.Sizeof(CodeDirectory{}))
    + hashOff := idOff + int64(len(id)+1)
    + cdirSz := hashOff + nhashes*sha256.Size
    + return int64(unsafe.Sizeof(SuperBlob{})+unsafe.Sizeof(Blob{})) + cdirSz
    +}
    +
    +// Sign generates an ad-hoc code signature and writes it to out.
    +// data is the file content without the signature, of size codeSize.
    +// textOff and textSize is the file offset and size of the text segment.
    +// isMain is true if thie is a main executable.
    +func Sign(out []byte, data io.Reader, id string, codeSize, textOff, textSize int64, isMain bool) {
    + nhashes := (codeSize + pageSize - 1) / pageSize
    + idOff := int64(unsafe.Sizeof(CodeDirectory{}))
    + hashOff := idOff + int64(len(id)+1)
    + sz := Size(codeSize, id)
    +
    + // emit blob headers
    + sb := SuperBlob{
    + magic: CSMAGIC_EMBEDDED_SIGNATURE,
    + length: uint32(sz),
    + count: 1,
    + }
    + blob := Blob{
    + typ: CSSLOT_CODEDIRECTORY,
    + offset: uint32(unsafe.Sizeof(SuperBlob{}) + unsafe.Sizeof(Blob{})),
    + }
    + cdir := CodeDirectory{
    + magic: CSMAGIC_CODEDIRECTORY,
    + length: uint32(sz) - uint32(unsafe.Sizeof(SuperBlob{})+unsafe.Sizeof(Blob{})),
    + version: 0x20400,
    + flags: 0x20002, // adhoc | linkerSigned
    + hashOffset: uint32(hashOff),
    + identOffset: uint32(idOff),
    + nCodeSlots: uint32(nhashes),
    + codeLimit: uint32(codeSize),
    + hashSize: sha256.Size,
    + hashType: kSecCodeSignatureHashSHA256,
    + pageSize: uint8(pageSizeBits),
    + execSegBase: uint64(textOff),
    + execSegLimit: uint64(textSize),
    + }
    + if isMain {
    + cdir.execSegFlags = CS_EXECSEG_MAIN_BINARY
    + }
    +
    + outp := out
    + outp = sb.put(outp)
    + outp = blob.put(outp)
    + outp = cdir.put(outp)
    + outp = puts(outp, []byte(id+"\000"))
    +
    + // emit hashes
    + var buf [pageSize]byte
    + p := 0
    + for p < int(codeSize) {
    + n, err := io.ReadFull(data, buf[:])
    + if err == io.EOF {
    + break
    + }
    + if err != nil && err != io.ErrUnexpectedEOF {
    + panic(err)
    + }
    + if p+n > int(codeSize) {
    + n = int(codeSize) - p
    + }
    + p += n
    + h := sha256.New()
    + h.Write(buf[:n])
    + b := h.Sum(nil)
    + outp = puts(outp, b[:])
    + }
    +}
    diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
    index 51abefc887..d439754477 100644
    --- a/src/cmd/link/internal/ld/macho.go
    +++ b/src/cmd/link/internal/ld/macho.go
    @@ -6,6 +6,7 @@ package ld

    import (
    "bytes"
    + "cmd/internal/codesign"
    "cmd/internal/objabi"
    "cmd/internal/sys"
    "cmd/link/internal/loader"
    @@ -643,6 +644,8 @@ func asmbMacho(ctxt *Link) {
    }
    ctxt.Out.SeekSet(0)

    + ldr := ctxt.loader
    +
    /* apple MACH */
    va := *FlagTextAddr - int64(HEADR)

    @@ -757,9 +760,8 @@ func asmbMacho(ctxt *Link) {
    }
    }

    + var codesigOff int64
    if !*FlagD {
    - ldr := ctxt.loader
    -
    // must match domacholink below
    s1 := ldr.SymSize(ldr.Lookup(".machorebase", 0))
    s2 := ldr.SymSize(ldr.Lookup(".machobind", 0))
    @@ -767,15 +769,18 @@ func asmbMacho(ctxt *Link) {
    s4 := ldr.SymSize(ctxt.ArchSyms.LinkEditPLT)
    s5 := ldr.SymSize(ctxt.ArchSyms.LinkEditGOT)
    s6 := ldr.SymSize(ldr.Lookup(".machosymstr", 0))
    + s7 := ldr.SymSize(ldr.Lookup(".machocodesig", 0))

    if ctxt.LinkMode != LinkExternal {
    ms := newMachoSeg("__LINKEDIT", 0)
    ms.vaddr = uint64(Rnd(int64(Segdata.Vaddr+Segdata.Length), int64(*FlagRound)))
    - ms.vsize = uint64(s1 + s2 + s3 + s4 + s5 + s6)
    + ms.vsize = uint64(s1 + s2 + s3 + s4 + s5 + s6 + s7)
    ms.fileoffset = uint64(linkoff)
    ms.filesize = ms.vsize
    ms.prot1 = 1
    ms.prot2 = 1
    +
    + codesigOff = linkoff + s1 + s2 + s3 + s4 + s5 + s6
    }

    if ctxt.LinkMode != LinkExternal && ctxt.IsPIE() {
    @@ -814,12 +819,31 @@ func asmbMacho(ctxt *Link) {
    stringtouint32(ml.data[4:], lib)
    }
    }
    +
    + if ctxt.LinkMode != LinkExternal && objabi.GOOS == "darwin" && ctxt.IsARM64() {
    + ml := newMachoLoad(ctxt.Arch, LC_CODE_SIGNATURE, 2)
    + ml.data[0] = uint32(codesigOff)
    + ml.data[1] = uint32(s7)
    + }
    }

    a := machowrite(ctxt, ctxt.Arch, ctxt.Out, ctxt.LinkMode)
    if int32(a) > HEADR {
    Exitf("HEADR too small: %d > %d", a, HEADR)
    }
    +
    + // Now we have written everything. Compute the code signature (which
    + // is a hash of the file content, so it must be done at last.)
    + if ctxt.IsInternal() && objabi.GOOS == "darwin" && ctxt.IsARM64() {
    + cs := ldr.Lookup(".machocodesig", 0)
    + data := ctxt.Out.Data()
    + if int64(len(data)) != codesigOff {
    + panic("wrong size")
    + }
    + codesign.Sign(ldr.Data(cs), bytes.NewReader(data), "a.out", codesigOff, int64(Segtext.Fileoff), int64(Segtext.Filelen), ctxt.IsExe() || ctxt.IsPIE())
    + ctxt.Out.SeekSet(codesigOff)
    + ctxt.Out.Write(ldr.Data(cs))
    + }
    }

    func symkind(ldr *loader.Loader, s loader.Sym) int {
    @@ -1057,7 +1081,6 @@ func machodysymtab(ctxt *Link, base int64) {

    func doMachoLink(ctxt *Link) int64 {
    machosymtab(ctxt)
    -
    machoDyldInfo(ctxt)

    ldr := ctxt.loader
    @@ -1070,6 +1093,8 @@ func doMachoLink(ctxt *Link) int64 {
    s5 := ctxt.ArchSyms.LinkEditGOT
    s6 := ldr.Lookup(".machosymstr", 0)

    + size := ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4) + ldr.SymSize(s5) + ldr.SymSize(s6)
    +
    // Force the linkedit section to end on a 16-byte
    // boundary. This allows pure (non-cgo) Go binaries
    // to be code signed correctly.
    @@ -1087,13 +1112,14 @@ func doMachoLink(ctxt *Link) int64 {
    // boundary, codesign_allocate will not need to apply
    // any alignment padding itself, working around the
    // issue.
    - s6b := ldr.MakeSymbolUpdater(s6)
    - for s6b.Size()%16 != 0 {
    - s6b.AddUint8(0)
    + if size%16 != 0 {
    + n := 16 - size%16
    + s6b := ldr.MakeSymbolUpdater(s6)
    + s6b.Grow(s6b.Size() + n)
    + s6b.SetSize(s6b.Size() + n)
    + size += n
    }

    - size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4) + ldr.SymSize(s5) + ldr.SymSize(s6))
    -
    if size > 0 {
    linkoff = Rnd(int64(uint64(HEADR)+Segtext.Length), int64(*FlagRound)) + Rnd(int64(Segrelrodata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdwarf.Filelen), int64(*FlagRound))
    ctxt.Out.SeekSet(linkoff)
    @@ -1104,9 +1130,12 @@ func doMachoLink(ctxt *Link) int64 {
    ctxt.Out.Write(ldr.Data(s4))
    ctxt.Out.Write(ldr.Data(s5))
    ctxt.Out.Write(ldr.Data(s6))
    +
    + s7 := machoCodeSigSym(ctxt, linkoff+size)
    + size += ldr.SymSize(s7)
    }

    - return Rnd(int64(size), int64(*FlagRound))
    + return Rnd(size, int64(*FlagRound))
    }

    func machorelocsect(ctxt *Link, out *OutBuf, sect *sym.Section, syms []loader.Sym) {
    @@ -1378,3 +1407,18 @@ func machoDyldInfo(ctxt *Link) {
    // e.g. dlsym'd. But internal linking is not the default in that case, so
    // it is fine.
    }
    +
    +// machoCodeSigSym creates and returns a symbol for code signature.
    +// The symbol context is left as zeros, which will be generated at the end
    +// (as it depends on the rest of the file).
    +func machoCodeSigSym(ctxt *Link, codeSize int64) loader.Sym {
    + ldr := ctxt.loader
    + cs := ldr.CreateSymForUpdate(".machocodesig", 0)
    + if objabi.GOOS != "darwin" || !ctxt.IsARM64() || ctxt.IsExternal() {
    + return cs.Sym()
    + }
    + sz := codesign.Size(codeSize, "a.out")
    + cs.Grow(sz)
    + cs.SetSize(sz)
    + return cs.Sym()
    +}
    diff --git a/src/cmd/link/internal/ld/macho_combine_dwarf.go b/src/cmd/link/internal/ld/macho_combine_dwarf.go
    index 77ee8a4d62..fc57fe7d0f 100644
    --- a/src/cmd/link/internal/ld/macho_combine_dwarf.go
    +++ b/src/cmd/link/internal/ld/macho_combine_dwarf.go
    @@ -6,6 +6,7 @@ package ld

    import (
    "bytes"
    + "cmd/internal/codesign"
    "compress/zlib"
    "debug/macho"
    "encoding/binary"
    @@ -204,6 +205,8 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    return err
    }

    + var outlinkseg macho.Segment64
    + var outlinksegCmdOff, codeSigOff, codeSigSize int64
    reader := loadCmdReader{next: int64(cmdOffset), f: outf, order: exem.ByteOrder}
    for i := uint32(0); i < exem.Ncmd; i++ {
    cmd, err := reader.Next()
    @@ -213,7 +216,12 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    linkoffset := uint64(linkstart) - linkseg.Offset
    switch cmd.Cmd {
    case macho.LoadCmdSegment64:
    - err = machoUpdateSegment(reader, linkseg, linkoffset)
    + var seg macho.Segment64
    + err = machoUpdateSegment(reader, &seg, linkseg, linkoffset)
    + if seg.Name == [len(seg.Name)]byte{'_', '_', 'L', 'I', 'N', 'K', 'E', 'D', 'I', 'T'} {
    + outlinkseg = seg
    + outlinksegCmdOff = reader.offset
    + }
    case macho.LoadCmdSegment:
    panic("unexpected 32-bit segment")
    case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
    @@ -222,7 +230,18 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.SymtabCmd{}, "Symoff", "Stroff")
    case macho.LoadCmdDysymtab:
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &macho.DysymtabCmd{}, "Tocoffset", "Modtaboff", "Extrefsymoff", "Indirectsymoff", "Extreloff", "Locreloff")
    - case LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
    + case LC_CODE_SIGNATURE:
    + cmd := linkEditDataCmd{}
    + err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &cmd, "DataOff")
    + if err != nil {
    + return err
    + }
    + // We also need to update code signature's size.
    + codeSigOff = int64(cmd.DataOff)
    + codeSigSize = codesign.Size(codeSigOff, "a.out")
    + cmd.DataLen = uint32(codeSigSize)
    + err = reader.WriteAt(0, cmd)
    + case LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS:
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &linkEditDataCmd{}, "DataOff")
    case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
    err = machoUpdateLoadCommand(reader, linkseg, linkoffset, &encryptionInfoCmd{}, "CryptOff")
    @@ -236,7 +255,33 @@ func machoCombineDwarf(ctxt *Link, exef *os.File, exem *macho.File, dsym, outexe
    }
    }
    // Do the final update of the DWARF segment's load command.
    - return machoUpdateDwarfHeader(&reader, compressedSects, dwarfsize, dwarfstart, realdwarf)
    + err = machoUpdateDwarfHeader(&reader, compressedSects, dwarfsize, dwarfstart, realdwarf)
    + if err != nil {
    + return err
    + }
    +
    + if codeSigOff != 0 {
    + // Code signature's size changed. Fix up __LINKEDIT segment's size.
    + if outlinksegCmdOff == 0 {
    + panic("__LINKEDIT segment not found")
    + }
    + reader.offset = outlinksegCmdOff
    + outlinkseg.Filesz = uint64(codeSigOff+codeSigSize) - outlinkseg.Offset
    + outlinkseg.Memsz = outlinkseg.Filesz
    + reader.WriteAt(0, outlinkseg)
    +
    + // Update code signature, if any. This needs to be done at last, as
    + // the signature depends on the rest of the file.
    + text := exem.Segment("__TEXT")
    + cs := make([]byte, codeSigSize)
    + _, err = outf.Seek(0, os.SEEK_SET)
    + if err != nil {
    + return err
    + }
    + codesign.Sign(cs, outf, "a.out", codeSigOff, int64(text.Offset), int64(text.Filesz), ctxt.IsExe() || ctxt.IsPIE())
    + _, err = outf.WriteAt(cs, codeSigOff)
    + }
    + return err
    }

    // machoCompressSections tries to compress the DWARF segments in dwarfm,
    @@ -309,9 +354,8 @@ func machoCompressSection(sectBytes []byte) (compressed bool, contents []byte, e

    // machoUpdateSegment updates the load command for a moved segment.
    // Only the linkedit segment should move, and it should have 0 sections.
    -func machoUpdateSegment(r loadCmdReader, linkseg *macho.Segment, linkoffset uint64) error {
    - var seg macho.Segment64
    - if err := r.ReadAt(0, &seg); err != nil {
    +func machoUpdateSegment(r loadCmdReader, seg *macho.Segment64, linkseg *macho.Segment, linkoffset uint64) error {
    + if err := r.ReadAt(0, seg); err != nil {
    return err
    }

    @@ -320,11 +364,11 @@ func machoUpdateSegment(r loadCmdReader, linkseg *macho.Segment, linkoffset uint
    return nil
    }
    seg.Offset += linkoffset
    - if err := r.WriteAt(0, &seg); err != nil {
    + if err := r.WriteAt(0, seg); err != nil {
    return err
    }
    // There shouldn't be any sections, but just to make sure...
    - return machoUpdateSections(r, &seg, linkoffset, nil)
    + return machoUpdateSections(r, seg, linkoffset, nil)
    }

    func machoUpdateSections(r loadCmdReader, seg *macho.Segment64, deltaOffset uint64, compressedSects []*macho.Section) error {
    diff --git a/src/cmd/link/internal/ld/outbuf.go b/src/cmd/link/internal/ld/outbuf.go
    index 36ec394077..dda7d83f18 100644
    --- a/src/cmd/link/internal/ld/outbuf.go
    +++ b/src/cmd/link/internal/ld/outbuf.go
    @@ -113,6 +113,7 @@ func (out *OutBuf) Close() error {
    }
    if out.isMmapped() {
    out.copyHeap()
    + out.purgeKernelCache()
    out.munmap()
    }
    if out.f == nil {
    @@ -135,6 +136,15 @@ func (out *OutBuf) isMmapped() bool {
    return len(out.buf) != 0
    }

    +// Data returns the whole written OutBuf as a byte slice.
    +func (out *OutBuf) Data() []byte {
    + if out.isMmapped() {
    + out.copyHeap()
    + return out.buf
    + }
    + return out.heap
    +}
    +
    // copyHeap copies the heap to the mmapped section of memory, returning true if
    // a copy takes place.
    func (out *OutBuf) copyHeap() bool {
    diff --git a/src/cmd/link/internal/ld/outbuf_darwin.go b/src/cmd/link/internal/ld/outbuf_darwin.go
    index d7e3372230..402543363d 100644
    --- a/src/cmd/link/internal/ld/outbuf_darwin.go
    +++ b/src/cmd/link/internal/ld/outbuf_darwin.go
    @@ -36,3 +36,8 @@ func (out *OutBuf) fallocate(size uint64) error {

    return nil
    }
    +
    +func (out *OutBuf) purgeKernelCache() {
    + syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(&out.buf[0])), uintptr(len(out.buf)), syscall.MS_INVALIDATE)
    + // Best effort. Ignore error.
    +}
    diff --git a/src/cmd/link/internal/ld/outbuf_notdarwin.go b/src/cmd/link/internal/ld/outbuf_notdarwin.go
    new file mode 100644
    index 0000000000..f7cac4ca0f
    --- /dev/null
    +++ b/src/cmd/link/internal/ld/outbuf_notdarwin.go
    @@ -0,0 +1,9 @@
    +// Copyright 2020 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.
    +
    +// +build !darwin
    +
    +package ld
    +
    +func (out *OutBuf) purgeKernelCache() {}