From 0bb2183d45a1acec07470d9f041c55377217c8e2 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Tue, 8 Oct 2024 12:42:33 -0400 Subject: [PATCH] cmd/internal/buildid: skip over Mach-O UUID from buildid computation With the "-B gobuildid" linker option (which will be the default on some platforms), the host build ID (GNU build ID, Mach-O UUID) depends on the Go buildid. If the host build ID is included in the Go buildid computation, it will lead to convergence problem for the toolchain binaries. So ignore the host build ID in the buildid computation. This CL only handles Mach-O UUID. ELF GNU build ID will be handled later. For #68678. For #63934. Cq-Include-Trybots: luci.golang.try:gotip-darwin-amd64_14,gotip-darwin-arm64_13 Change-Id: Ie8ff20402a1c6083246d25dea391140c75be40d0 Reviewed-on: https://go-review.googlesource.com/c/go/+/618597 Reviewed-by: Michael Knyszek LUCI-TryBot-Result: Go LUCI Reviewed-by: Than McIntosh --- src/cmd/internal/buildid/rewrite.go | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/cmd/internal/buildid/rewrite.go b/src/cmd/internal/buildid/rewrite.go index becc078242..26720afef4 100644 --- a/src/cmd/internal/buildid/rewrite.go +++ b/src/cmd/internal/buildid/rewrite.go @@ -7,6 +7,7 @@ package buildid import ( "bytes" "cmd/internal/codesign" + imacho "cmd/internal/macho" "crypto/sha256" "debug/macho" "fmt" @@ -31,11 +32,18 @@ func FindAndHash(r io.Reader, id string, bufSize int) (matches []int64, hash [32 zeros := make([]byte, len(id)) idBytes := []byte(id) + r0 := r // preserve original type of r + // 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) + // With the "-B gobuildid" linker option (which will be the default on some + // platforms), the host build ID (GNU build ID, Mach-O UUID) depends on the + // Go buildid. So ignore the host build ID, to avoid convergence problem. + r = excludeHostBuildID(r, r0) + // 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. @@ -124,6 +132,14 @@ func excludeMachoCodeSignature(r io.Reader) io.Reader { return &excludedReader{r, 0, int64(cmd.Dataoff), int64(cmd.Dataoff + cmd.Datasize)} } +func excludeHostBuildID(r, r0 io.Reader) io.Reader { + off, sz, ok := findHostBuildID(r0) + if !ok { + return r + } + return &excludedReader{r, 0, off, off + sz} +} + // 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. @@ -163,3 +179,29 @@ func findMachoCodeSignature(r any) (*macho.File, codesign.CodeSigCmd, bool) { cmd, ok := codesign.FindCodeSigCmd(f) return f, cmd, ok } + +func findHostBuildID(r io.Reader) (offset int64, size int64, ok bool) { + ra, ok := r.(io.ReaderAt) + if !ok { + return 0, 0, false + } + // TODO: handle ELF GNU build ID. + f, err := macho.NewFile(ra) + if err != nil { + return 0, 0, false + } + + reader := imacho.NewLoadCmdReader(io.NewSectionReader(ra, 0, 1<<63-1), f.ByteOrder, imacho.FileHeaderSize(f)) + for i := uint32(0); i < f.Ncmd; i++ { + cmd, err := reader.Next() + if err != nil { + break + } + if cmd.Cmd == imacho.LC_UUID { + // The UUID is the data in the LC_UUID load command, + // skipping over the 8-byte command header. + return int64(reader.Offset() + 8), int64(cmd.Len - 8), true + } + } + return 0, 0, false +} -- 2.48.1