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  	"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  

View as plain text