Source file src/cmd/link/internal/ld/macho_update_uuid.go
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 imacho "cmd/internal/macho" 22 23 "debug/macho" 24 "io" 25 "os" 26 ) 27 28 // uuidFromHash converts a hash to a UUID 29 // suitable for use in the Macho LC_UUID load command. 30 func uuidFromHash(hashed [32]byte) []byte { 31 rv := make([]byte, 16) 32 copy(rv, hashed[:16]) 33 34 // Mark the UUID as version 3, variant 1, which is an MD5-hash-based UUID. 35 // We are not actually using MD5, but we're also not using SHA1 (version 5), 36 // and LLVM also uses version 3 [1] so this seems good enough. 37 // 38 // [1] https://github.com/llvm/llvm-project/blob/2a3a79ce4c2149d7787d56f9841b66cacc9061d0/lld/MachO/Writer.cpp#L524 39 rv[6] = (rv[6] &^ 0xF0) | 0x30 // version 3 40 rv[8] = (rv[8] &^ 0xC0) | 0x80 // variant 1 41 42 return rv 43 } 44 45 // machoRewriteUuid copies over the contents of the Macho executable 46 // exef into the output file outexe, and in the process updates the 47 // LC_UUID command to a new value recomputed from the Go build id. 48 func machoRewriteUuid(ctxt *Link, exef *os.File, exem *macho.File, outexe string) error { 49 outf, err := os.OpenFile(outexe, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) 50 if err != nil { 51 return err 52 } 53 defer outf.Close() 54 55 // Copy over the file. 56 if _, err := io.Copy(outf, exef); err != nil { 57 return err 58 } 59 60 // Locate the portion of the binary containing the load commands. 61 cmdOffset := imacho.FileHeaderSize(exem) 62 if _, err := outf.Seek(cmdOffset, 0); err != nil { 63 return err 64 } 65 66 // Read the load commands, looking for the LC_UUID cmd. If/when we 67 // locate it, overwrite it with a new value produced by 68 // uuidFromHash. 69 reader := imacho.NewLoadCmdUpdater(outf, exem.ByteOrder, cmdOffset) 70 for i := uint32(0); i < exem.Ncmd; i++ { 71 cmd, err := reader.Next() 72 if err != nil { 73 return err 74 } 75 if cmd.Cmd == imacho.LC_UUID { 76 var u uuidCmd 77 if err := reader.ReadAt(0, &u); err != nil { 78 return err 79 } 80 clear(u.Uuid[:]) 81 copy(u.Uuid[:], buildinfo) 82 if err := reader.WriteAt(0, &u); err != nil { 83 return err 84 } 85 break 86 } 87 } 88 89 // We're done 90 return nil 91 } 92