]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/internal/codesign: new package
authorCherry Zhang <cherryyz@google.com>
Sun, 22 Nov 2020 02:33:18 +0000 (21:33 -0500)
committerCherry Zhang <cherryyz@google.com>
Tue, 1 Dec 2020 21:41:39 +0000 (21:41 +0000)
On macOS/ARM64, the kernel requires that binaries must have a
valid code signature to run. The C toolchain code-signs the
binary at link time. We do the same.

It is more subtle for Go because we stamp the buildid after
linking. As the signature contains hashes of the entire file
(except the signature itself), we must (re)generate the signature
after stamping the buildid.

This CL adds a new codesign package, which provides
functionality to generate the code signature. It is a separate
internal package so it can be used both in the linker and by the
go command. The next CLs will add code-signing to the linker and
the go command.

Updates #38485, #42684.

Change-Id: Id46801a6665beebaab0eb413ff2e64c5b9467059
Reviewed-on: https://go-review.googlesource.com/c/go/+/272254
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/dist/buildtool.go
src/cmd/internal/codesign/codesign.go [new file with mode: 0644]

index 37b3d45977a888746a3bd16eae132ec228612431..e7bedfb84eec506061b2c3a7ba3733b8ac9fbe63 100644 (file)
@@ -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/internal/codesign/codesign.go b/src/cmd/internal/codesign/codesign.go
new file mode 100644 (file)
index 0000000..0517a10
--- /dev/null
@@ -0,0 +1,268 @@
+// 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.
+//
+// This is not a general tool for code-signing. It is made
+// specifically for the Go toolchain. It uses the same
+// ad-hoc signing algorithm as the Darwin linker.
+package codesign
+
+import (
+       "crypto/sha256"
+       "debug/macho"
+       "encoding/binary"
+       "io"
+)
+
+// Code signature layout.
+//
+// The code signature is a block of bytes that contains
+// a SuperBlob, which contains one or more Blobs. For ad-hoc
+// signing, a single CodeDirectory Blob suffices.
+//
+// A SuperBlob starts with its header (the binary representation
+// of the SuperBlob struct), followed by a list of (in our case,
+// one) Blobs (offset and size). A CodeDirectory Blob starts
+// with its head (the binary representation of CodeDirectory struct),
+// followed by the identifier (as a C string) and the hashes, at
+// the corresponding offsets.
+//
+// The signature data must be included in the __LINKEDIT segment.
+// In the Mach-O file header, an LC_CODE_SIGNATURE load command
+// points to the data.
+
+const (
+       pageSizeBits = 12
+       pageSize     = 1 << pageSizeBits
+)
+
+const LC_CODE_SIGNATURE = 0x1d
+
+// Constants and struct layouts are from
+// https://opensource.apple.com/source/xnu/xnu-4903.270.47/osfmk/kern/cs_blobs.h
+
+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 (
+       CS_HASHTYPE_SHA1             = 1
+       CS_HASHTYPE_SHA256           = 2
+       CS_HASHTYPE_SHA256_TRUNCATED = 3
+       CS_HASHTYPE_SHA384           = 4
+)
+
+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
+}
+
+const blobSize = 2 * 4
+
+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
+}
+
+const superBlobSize = 3 * 4
+
+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
+}
+
+const codeDirectorySize = 13*4 + 4 + 4*8
+
+// 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 put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
+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:] }
+
+// Size computes the size of the code signature.
+// id is the identifier used for signing (a field in CodeDirectory blob, which
+// has no significance in ad-hoc signing).
+func Size(codeSize int64, id string) int64 {
+       nhashes := (codeSize + pageSize - 1) / pageSize
+       idOff := int64(codeDirectorySize)
+       hashOff := idOff + int64(len(id)+1)
+       cdirSz := hashOff + nhashes*sha256.Size
+       return int64(superBlobSize+blobSize) + cdirSz
+}
+
+// Sign generates an ad-hoc code signature and writes it to out.
+// out must have length at least Size(codeSize, id).
+// 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 this is a main executable.
+// id is the identifier used for signing (a field in CodeDirectory blob, which
+// has no significance in ad-hoc signing).
+func Sign(out []byte, data io.Reader, id string, codeSize, textOff, textSize int64, isMain bool) {
+       nhashes := (codeSize + pageSize - 1) / pageSize
+       idOff := int64(codeDirectorySize)
+       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: superBlobSize + blobSize,
+       }
+       cdir := CodeDirectory{
+               magic:        CSMAGIC_CODEDIRECTORY,
+               length:       uint32(sz) - (superBlobSize + blobSize),
+               version:      0x20400,
+               flags:        0x20002, // adhoc | linkerSigned
+               hashOffset:   uint32(hashOff),
+               identOffset:  uint32(idOff),
+               nCodeSlots:   uint32(nhashes),
+               codeLimit:    uint32(codeSize),
+               hashSize:     sha256.Size,
+               hashType:     CS_HASHTYPE_SHA256,
+               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)
+
+       // emit the identifier
+       outp = puts(outp, []byte(id+"\000"))
+
+       // emit hashes
+       var buf [pageSize]byte
+       h := sha256.New()
+       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.Reset()
+               h.Write(buf[:n])
+               b := h.Sum(nil)
+               outp = puts(outp, b[:])
+       }
+}