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  

View as plain text