]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: code-sign on darwin/arm64
authorCherry Zhang <cherryyz@google.com>
Sun, 22 Nov 2020 01:11:03 +0000 (20:11 -0500)
committerCherry Zhang <cherryyz@google.com>
Tue, 1 Dec 2020 23:14:14 +0000 (23:14 +0000)
This CL lets the linker code-sign output binaries on
darwin/arm64, as the kernel requires binaries must be signed in
order to run.

This signature will likely be invalidated when we stamp the
buildid after linking. We still do it in the linker, for
- plain "go tool link" works.
- the linker generates the LC_CODE_SIGNATURE load command with
  the right size and offset, so we don't need to update it when
  stamping the buildid.

Updates #38485, #42684.

Change-Id: Ia306328906d73217221ba31093fe61a935a46122
Reviewed-on: https://go-review.googlesource.com/c/go/+/272256
Trust: Cherry Zhang <cherryyz@google.com>
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/macho.go
src/cmd/link/internal/ld/outbuf.go

index 735b84d37d35c4397fe96645261cc8bb0e1211c1..e1cc7184de3671a129f7f07dd958a94a8469a8dc 100644 (file)
@@ -298,6 +298,11 @@ func (ctxt *Link) CanUsePlugins() bool {
        return ctxt.canUsePlugins
 }
 
+// NeedCodeSign reports whether we need to code-sign the output binary.
+func (ctxt *Link) NeedCodeSign() bool {
+       return ctxt.IsDarwin() && ctxt.IsARM64()
+}
+
 var (
        dynlib          []string
        ldflag          []string
@@ -1642,6 +1647,12 @@ func (ctxt *Link) hostlink() {
                        Exitf("%s: %v", os.Args[0], err)
                }
        }
+       if ctxt.NeedCodeSign() {
+               err := machoCodeSign(ctxt, *flagOutfile)
+               if err != nil {
+                       Exitf("%s: code signing failed: %v", os.Args[0], err)
+               }
+       }
 }
 
 var createTrivialCOnce sync.Once
index 51abefc8875dba9d59f4caa4119ed99a85b25cda..f45957642024471f3c1c371c7491367a9e8cd458 100644 (file)
@@ -6,6 +6,7 @@ package ld
 
 import (
        "bytes"
+       "cmd/internal/codesign"
        "cmd/internal/objabi"
        "cmd/internal/sys"
        "cmd/link/internal/loader"
@@ -17,6 +18,7 @@ import (
        "os"
        "sort"
        "strings"
+       "unsafe"
 )
 
 type MachoHdr struct {
@@ -245,6 +247,8 @@ const (
        BIND_SUBOPCODE_THREADED_APPLY                            = 0x01
 )
 
+const machoHeaderSize64 = 8 * 4 // size of 64-bit Mach-O header
+
 // Mach-O file writing
 // https://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
 
@@ -643,6 +647,8 @@ func asmbMacho(ctxt *Link) {
        }
        ctxt.Out.SeekSet(0)
 
+       ldr := ctxt.loader
+
        /* apple MACH */
        va := *FlagTextAddr - int64(HEADR)
 
@@ -757,25 +763,27 @@ func asmbMacho(ctxt *Link) {
                }
        }
 
+       var codesigOff int64
        if !*FlagD {
-               ldr := ctxt.loader
-
-               // must match domacholink below
+               // must match doMachoLink below
                s1 := ldr.SymSize(ldr.Lookup(".machorebase", 0))
                s2 := ldr.SymSize(ldr.Lookup(".machobind", 0))
                s3 := ldr.SymSize(ldr.Lookup(".machosymtab", 0))
                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 +822,31 @@ func asmbMacho(ctxt *Link) {
                                stringtouint32(ml.data[4:], lib)
                        }
                }
+
+               if ctxt.IsInternal() && ctxt.NeedCodeSign() {
+                       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() && ctxt.NeedCodeSign() {
+               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 +1084,6 @@ func machodysymtab(ctxt *Link, base int64) {
 
 func doMachoLink(ctxt *Link) int64 {
        machosymtab(ctxt)
-
        machoDyldInfo(ctxt)
 
        ldr := ctxt.loader
@@ -1070,6 +1096,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 +1115,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 +1133,13 @@ func doMachoLink(ctxt *Link) int64 {
                ctxt.Out.Write(ldr.Data(s4))
                ctxt.Out.Write(ldr.Data(s5))
                ctxt.Out.Write(ldr.Data(s6))
+
+               // Add code signature if necessary. This must be the last.
+               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 +1411,94 @@ 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 !ctxt.NeedCodeSign() || ctxt.IsExternal() {
+               return cs.Sym()
+       }
+       sz := codesign.Size(codeSize, "a.out")
+       cs.Grow(sz)
+       cs.SetSize(sz)
+       return cs.Sym()
+}
+
+// machoCodeSign code-signs Mach-O file fname with an ad-hoc signature.
+// This is used for updating an external linker generated binary.
+func machoCodeSign(ctxt *Link, fname string) error {
+       f, err := os.OpenFile(fname, os.O_RDWR, 0)
+       if err != nil {
+               return err
+       }
+       defer f.Close()
+
+       mf, err := macho.NewFile(f)
+       if err != nil {
+               return err
+       }
+       if mf.Magic != macho.Magic64 {
+               Exitf("not 64-bit Mach-O file: %s", fname)
+       }
+
+       // Find existing LC_CODE_SIGNATURE and __LINKEDIT segment
+       var sigOff, sigSz, csCmdOff, linkeditOff int64
+       var linkeditSeg, textSeg *macho.Segment
+       loadOff := int64(machoHeaderSize64)
+       get32 := mf.ByteOrder.Uint32
+       for _, l := range mf.Loads {
+               data := l.Raw()
+               cmd, sz := get32(data), get32(data[4:])
+               if cmd == LC_CODE_SIGNATURE {
+                       sigOff = int64(get32(data[8:]))
+                       sigSz = int64(get32(data[12:]))
+                       csCmdOff = loadOff
+               }
+               if seg, ok := l.(*macho.Segment); ok {
+                       switch seg.Name {
+                       case "__LINKEDIT":
+                               linkeditSeg = seg
+                               linkeditOff = loadOff
+                       case "__TEXT":
+                               textSeg = seg
+                       }
+               }
+               loadOff += int64(sz)
+       }
+
+       if sigOff == 0 {
+               // The C linker doesn't generate a signed binary, for some reason.
+               // Skip.
+               return nil
+       }
+       sz := codesign.Size(sigOff, "a.out")
+       if sz != sigSz {
+               // Update the load command,
+               var tmp [8]byte
+               mf.ByteOrder.PutUint32(tmp[:4], uint32(sz))
+               _, err = f.WriteAt(tmp[:4], csCmdOff+12)
+               if err != nil {
+                       return err
+               }
+
+               // Uodate the __LINKEDIT segment.
+               segSz := sigOff + sz - int64(linkeditSeg.Offset)
+               mf.ByteOrder.PutUint64(tmp[:8], uint64(segSz))
+               _, err = f.WriteAt(tmp[:8], int64(linkeditOff)+int64(unsafe.Offsetof(macho.Segment64{}.Memsz)))
+               if err != nil {
+                       return err
+               }
+               _, err = f.WriteAt(tmp[:8], int64(linkeditOff)+int64(unsafe.Offsetof(macho.Segment64{}.Filesz)))
+               if err != nil {
+                       return err
+               }
+       }
+
+       cs := make([]byte, sz)
+       codesign.Sign(cs, f, "a.out", sigOff, int64(textSeg.Offset), int64(textSeg.Filesz), ctxt.IsExe() || ctxt.IsPIE())
+       _, err = f.WriteAt(cs, sigOff)
+       return err
+}
index 36ec394077035535aa5aac4e21d9b388d4931f3c..6cae064679da2ba17c74f0e5023ca43233a2c456 100644 (file)
@@ -135,6 +135,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 {