Last active
December 18, 2020 00:54
-
-
Save felixbuenemann/d08875fc3e1a69fe8d6eaa724829c950 to your computer and use it in GitHub Desktop.
Revisions
-
felixbuenemann revised this gist
Dec 2, 2020 . 1 changed file with 7 additions and 15 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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}" 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 -
felixbuenemann revised this gist
Dec 2, 2020 . 1 changed file with 0 additions and 682 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -27,8 +27,6 @@ class Go < Formula # sha256 "7d39badbe6096ffd120c3e997ee16fd026d12c4037eb055d26290a39f2689582" => :high_sierra # end head do url "https://go.googlesource.com/go.git" @@ -128,683 +126,3 @@ def install system bin/"go", "build", "hello.go" end end -
felixbuenemann created this gist
Dec 2, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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() {}