1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ld 6 7 // This file provides helper functions for updating/rewriting the UUID 8 // load command within a Go go binary generated on Darwin using 9 // external linking. Why is it necessary to update the UUID load 10 // command? See issue #64947 for more detail, but the short answer is 11 // that newer versions of the Macos toolchain (the newer linker in 12 // particular) appear to compute the UUID based not just on the 13 // content of the object files being linked but also on things like 14 // the timestamps/paths of the objects; this makes it 15 // difficult/impossible to support reproducible builds. Since we try 16 // hard to maintain build reproducibility for Go, the APIs here 17 // compute a new UUID (based on the Go build ID) and write it to the 18 // final executable generated by the external linker. 19 20 import ( 21 "cmd/internal/notsha256" 22 "debug/macho" 23 "io" 24 "os" 25 "unsafe" 26 ) 27 28 // uuidFromGoBuildId hashes the Go build ID and returns a slice of 16 29 // bytes suitable for use as the payload in a Macho LC_UUID load 30 // command. 31 func uuidFromGoBuildId(buildID string) []byte { 32 if buildID == "" { 33 return make([]byte, 16) 34 } 35 hashedBuildID := notsha256.Sum256([]byte(buildID)) 36 rv := hashedBuildID[:16] 37 38 // RFC 4122 conformance (see RFC 4122 Sections 4.2.2, 4.1.3). We 39 // want the "version" of this UUID to appear as 'hashed' as opposed 40 // to random or time-based. This is something of a fiction since 41 // we're not actually hashing using MD5 or SHA1, but it seems better 42 // to use this UUID flavor than any of the others. This is similar 43 // to how other linkers handle this (for example this code in lld: 44 // https://github.com/llvm/llvm-project/blob/2a3a79ce4c2149d7787d56f9841b66cacc9061d0/lld/MachO/Writer.cpp#L524). 45 rv[6] &= 0x0f 46 rv[6] |= 0x30 47 rv[8] &= 0x3f 48 rv[8] |= 0xc0 49 50 return rv 51 } 52 53 // machoRewriteUuid copies over the contents of the Macho executable 54 // exef into the output file outexe, and in the process updates the 55 // LC_UUID command to a new value recomputed from the Go build id. 56 func machoRewriteUuid(ctxt *Link, exef *os.File, exem *macho.File, outexe string) error { 57 outf, err := os.OpenFile(outexe, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) 58 if err != nil { 59 return err 60 } 61 defer outf.Close() 62 63 // Copy over the file. 64 if _, err := io.Copy(outf, exef); err != nil { 65 return err 66 } 67 68 // Locate the portion of the binary containing the load commands. 69 cmdOffset := unsafe.Sizeof(exem.FileHeader) 70 if is64bit := exem.Magic == macho.Magic64; is64bit { 71 // mach_header_64 has one extra uint32. 72 cmdOffset += unsafe.Sizeof(exem.Magic) 73 } 74 if _, err := outf.Seek(int64(cmdOffset), 0); err != nil { 75 return err 76 } 77 78 // Read the load commands, looking for the LC_UUID cmd. If/when we 79 // locate it, overwrite it with a new value produced by 80 // uuidFromGoBuildId. 81 reader := loadCmdReader{next: int64(cmdOffset), 82 f: outf, order: exem.ByteOrder} 83 for i := uint32(0); i < exem.Ncmd; i++ { 84 cmd, err := reader.Next() 85 if err != nil { 86 return err 87 } 88 if cmd.Cmd == LC_UUID { 89 var u uuidCmd 90 if err := reader.ReadAt(0, &u); err != nil { 91 return err 92 } 93 copy(u.Uuid[:], uuidFromGoBuildId(*flagBuildid)) 94 if err := reader.WriteAt(0, &u); err != nil { 95 return err 96 } 97 break 98 } 99 } 100 101 // We're done 102 return nil 103 } 104