mirror of
https://github.com/golang/go
synced 2024-11-23 04:50:06 -07:00
cmd/internal/codesign: new package
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>
This commit is contained in:
parent
20e251864b
commit
7430266af4
1
src/cmd/dist/buildtool.go
vendored
1
src/cmd/dist/buildtool.go
vendored
@ -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",
|
||||
|
268
src/cmd/internal/codesign/codesign.go
Normal file
268
src/cmd/internal/codesign/codesign.go
Normal file
@ -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[:])
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user