mirror of
https://github.com/golang/go.git
synced 2025-05-30 11:51:34 +00:00
As the code signature contains hashes of the entire file (except the signature itself), rewriting buildid will invalidate the signature. This CL makes it regenerate the signature when rewriting the buildid. It only does it when the file already has a code signature, with proper size (darwin/arm64 binaries generated by the Go linker should have). Updates #38485, #42684. Change-Id: I082d9e5808b0ee6a35f9c362d7262aadd9113c81 Reviewed-on: https://go-review.googlesource.com/c/go/+/272257 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>
163 lines
5.0 KiB
Go
163 lines
5.0 KiB
Go
// Copyright 2017 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 buildid
|
|
|
|
import (
|
|
"bytes"
|
|
"cmd/internal/codesign"
|
|
"crypto/sha256"
|
|
"debug/macho"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// FindAndHash reads all of r and returns the offsets of occurrences of id.
|
|
// While reading, findAndHash also computes and returns
|
|
// a hash of the content of r, but with occurrences of id replaced by zeros.
|
|
// FindAndHash reads bufSize bytes from r at a time.
|
|
// If bufSize == 0, FindAndHash uses a reasonable default.
|
|
func FindAndHash(r io.Reader, id string, bufSize int) (matches []int64, hash [32]byte, err error) {
|
|
if bufSize == 0 {
|
|
bufSize = 31 * 1024 // bufSize+little will likely fit in 32 kB
|
|
}
|
|
if len(id) > bufSize {
|
|
return nil, [32]byte{}, fmt.Errorf("buildid.FindAndHash: buffer too small")
|
|
}
|
|
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 = excludeMachoCodeSignature(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.
|
|
// We allocate a tiny buffer (at least len(id)) and a big buffer (bufSize bytes)
|
|
// next to each other in memory and then copy the tail of
|
|
// one read into the tiny buffer before reading new data into the big buffer.
|
|
// The search for id is over the entire tiny+big buffer.
|
|
tiny := (len(id) + 127) &^ 127 // round up to 128-aligned
|
|
buf := make([]byte, tiny+bufSize)
|
|
h := sha256.New()
|
|
start := tiny
|
|
for offset := int64(0); ; {
|
|
// The file offset maintained by the loop corresponds to &buf[tiny].
|
|
// buf[start:tiny] is left over from previous iteration.
|
|
// After reading n bytes into buf[tiny:], we process buf[start:tiny+n].
|
|
n, err := io.ReadFull(r, buf[tiny:])
|
|
if err != io.ErrUnexpectedEOF && err != io.EOF && err != nil {
|
|
return nil, [32]byte{}, err
|
|
}
|
|
|
|
// Process any matches.
|
|
for {
|
|
i := bytes.Index(buf[start:tiny+n], idBytes)
|
|
if i < 0 {
|
|
break
|
|
}
|
|
matches = append(matches, offset+int64(start+i-tiny))
|
|
h.Write(buf[start : start+i])
|
|
h.Write(zeros)
|
|
start += i + len(id)
|
|
}
|
|
if n < bufSize {
|
|
// Did not fill buffer, must be at end of file.
|
|
h.Write(buf[start : tiny+n])
|
|
break
|
|
}
|
|
|
|
// Process all but final tiny bytes of buf (bufSize = len(buf)-tiny).
|
|
// Note that start > len(buf)-tiny is possible, if the search above
|
|
// found an id ending in the final tiny fringe. That's OK.
|
|
if start < len(buf)-tiny {
|
|
h.Write(buf[start : len(buf)-tiny])
|
|
start = len(buf) - tiny
|
|
}
|
|
|
|
// Slide ending tiny-sized fringe to beginning of buffer.
|
|
copy(buf[0:], buf[bufSize:])
|
|
start -= bufSize
|
|
offset += int64(bufSize)
|
|
}
|
|
h.Sum(hash[:0])
|
|
return matches, hash, nil
|
|
}
|
|
|
|
func Rewrite(w io.WriterAt, pos []int64, id string) error {
|
|
b := []byte(id)
|
|
for _, p := range pos {
|
|
if _, err := w.WriteAt(b, p); err != nil {
|
|
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) {
|
|
// Update the signature if the size matches, so we don't need to
|
|
// fix up headers. Binaries generated by the Go linker should have
|
|
// the expected size. Otherwise skip.
|
|
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 excludeMachoCodeSignature(r io.Reader) io.Reader {
|
|
_, cmd, ok := findMachoCodeSignature(r)
|
|
if !ok {
|
|
return r
|
|
}
|
|
return &excludedReader{r, 0, int64(cmd.Dataoff), int64(cmd.Dataoff + cmd.Datasize)}
|
|
}
|
|
|
|
// excludedReader wraps an io.Reader. Reading from it returns the bytes from
|
|
// the underlying reader, except that when the byte offset is within the
|
|
// range between start and end, it returns zero bytes.
|
|
type excludedReader struct {
|
|
r io.Reader
|
|
off int64 // current offset
|
|
start, end int64 // the range to be excluded (read as zero)
|
|
}
|
|
|
|
func (r *excludedReader) Read(p []byte) (int, error) {
|
|
n, err := r.r.Read(p)
|
|
if n > 0 && r.off+int64(n) > r.start && r.off < r.end {
|
|
cstart := r.start - r.off
|
|
if cstart < 0 {
|
|
cstart = 0
|
|
}
|
|
cend := r.end - r.off
|
|
if cend > int64(n) {
|
|
cend = int64(n)
|
|
}
|
|
zeros := make([]byte, cend-cstart)
|
|
copy(p[cstart:cend], zeros)
|
|
}
|
|
r.off += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
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
|
|
}
|