github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/modfetch/fetch.go (about)

     1  // Copyright 2018 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 modfetch
     6  
     7  import (
     8  	"archive/zip"
     9  	"bytes"
    10  	"context"
    11  	"crypto/sha256"
    12  	"encoding/base64"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"io/fs"
    17  	"os"
    18  	"path/filepath"
    19  	"sort"
    20  	"strings"
    21  	"sync"
    22  
    23  	"github.com/go-asm/go/cmd/go/base"
    24  	"github.com/go-asm/go/cmd/go/cfg"
    25  	"github.com/go-asm/go/cmd/go/fsys"
    26  	"github.com/go-asm/go/cmd/go/gover"
    27  	"github.com/go-asm/go/cmd/go/lockedfile"
    28  	"github.com/go-asm/go/cmd/go/par"
    29  	"github.com/go-asm/go/cmd/go/robustio"
    30  	"github.com/go-asm/go/cmd/go/str"
    31  	"github.com/go-asm/go/cmd/go/trace"
    32  
    33  	"golang.org/x/mod/module"
    34  	"golang.org/x/mod/sumdb/dirhash"
    35  	modzip "golang.org/x/mod/zip"
    36  )
    37  
    38  var downloadCache par.ErrCache[module.Version, string] // version → directory
    39  
    40  var ErrToolchain = errors.New("internal error: invalid operation on toolchain module")
    41  
    42  // Download downloads the specific module version to the
    43  // local download cache and returns the name of the directory
    44  // corresponding to the root of the module's file tree.
    45  func Download(ctx context.Context, mod module.Version) (dir string, err error) {
    46  	if gover.IsToolchain(mod.Path) {
    47  		return "", ErrToolchain
    48  	}
    49  	if err := checkCacheDir(ctx); err != nil {
    50  		base.Fatal(err)
    51  	}
    52  
    53  	// The par.Cache here avoids duplicate work.
    54  	return downloadCache.Do(mod, func() (string, error) {
    55  		dir, err := download(ctx, mod)
    56  		if err != nil {
    57  			return "", err
    58  		}
    59  		checkMod(ctx, mod)
    60  
    61  		// If go.mod exists (not an old legacy module), check version is not too new.
    62  		if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil {
    63  			goVersion := gover.GoModLookup(data, "go")
    64  			if gover.Compare(goVersion, gover.Local()) > 0 {
    65  				return "", &gover.TooNewError{What: mod.String(), GoVersion: goVersion}
    66  			}
    67  		} else if !errors.Is(err, fs.ErrNotExist) {
    68  			return "", err
    69  		}
    70  
    71  		return dir, nil
    72  	})
    73  }
    74  
    75  func download(ctx context.Context, mod module.Version) (dir string, err error) {
    76  	ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
    77  	defer span.Done()
    78  
    79  	dir, err = DownloadDir(ctx, mod)
    80  	if err == nil {
    81  		// The directory has already been completely extracted (no .partial file exists).
    82  		return dir, nil
    83  	} else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
    84  		return "", err
    85  	}
    86  
    87  	// To avoid cluttering the cache with extraneous files,
    88  	// DownloadZip uses the same lockfile as Download.
    89  	// Invoke DownloadZip before locking the file.
    90  	zipfile, err := DownloadZip(ctx, mod)
    91  	if err != nil {
    92  		return "", err
    93  	}
    94  
    95  	unlock, err := lockVersion(ctx, mod)
    96  	if err != nil {
    97  		return "", err
    98  	}
    99  	defer unlock()
   100  
   101  	ctx, span = trace.StartSpan(ctx, "unzip "+zipfile)
   102  	defer span.Done()
   103  
   104  	// Check whether the directory was populated while we were waiting on the lock.
   105  	_, dirErr := DownloadDir(ctx, mod)
   106  	if dirErr == nil {
   107  		return dir, nil
   108  	}
   109  	_, dirExists := dirErr.(*DownloadDirPartialError)
   110  
   111  	// Clean up any remaining temporary directories created by old versions
   112  	// (before 1.16), as well as partially extracted directories (indicated by
   113  	// DownloadDirPartialError, usually because of a .partial file). This is only
   114  	// safe to do because the lock file ensures that their writers are no longer
   115  	// active.
   116  	parentDir := filepath.Dir(dir)
   117  	tmpPrefix := filepath.Base(dir) + ".tmp-"
   118  	if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(parentDir), str.QuoteGlob(tmpPrefix)+"*")); err == nil {
   119  		for _, path := range old {
   120  			RemoveAll(path) // best effort
   121  		}
   122  	}
   123  	if dirExists {
   124  		if err := RemoveAll(dir); err != nil {
   125  			return "", err
   126  		}
   127  	}
   128  
   129  	partialPath, err := CachePath(ctx, mod, "partial")
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  
   134  	// Extract the module zip directory at its final location.
   135  	//
   136  	// To prevent other processes from reading the directory if we crash,
   137  	// create a .partial file before extracting the directory, and delete
   138  	// the .partial file afterward (all while holding the lock).
   139  	//
   140  	// Before Go 1.16, we extracted to a temporary directory with a random name
   141  	// then renamed it into place with os.Rename. On Windows, this failed with
   142  	// ERROR_ACCESS_DENIED when another process (usually an anti-virus scanner)
   143  	// opened files in the temporary directory.
   144  	//
   145  	// Go 1.14.2 and higher respect .partial files. Older versions may use
   146  	// partially extracted directories. 'go mod verify' can detect this,
   147  	// and 'go clean -modcache' can fix it.
   148  	if err := os.MkdirAll(parentDir, 0777); err != nil {
   149  		return "", err
   150  	}
   151  	if err := os.WriteFile(partialPath, nil, 0666); err != nil {
   152  		return "", err
   153  	}
   154  	if err := modzip.Unzip(dir, mod, zipfile); err != nil {
   155  		fmt.Fprintf(os.Stderr, "-> %s\n", err)
   156  		if rmErr := RemoveAll(dir); rmErr == nil {
   157  			os.Remove(partialPath)
   158  		}
   159  		return "", err
   160  	}
   161  	if err := os.Remove(partialPath); err != nil {
   162  		return "", err
   163  	}
   164  
   165  	if !cfg.ModCacheRW {
   166  		makeDirsReadOnly(dir)
   167  	}
   168  	return dir, nil
   169  }
   170  
   171  var downloadZipCache par.ErrCache[module.Version, string]
   172  
   173  // DownloadZip downloads the specific module version to the
   174  // local zip cache and returns the name of the zip file.
   175  func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
   176  	// The par.Cache here avoids duplicate work.
   177  	return downloadZipCache.Do(mod, func() (string, error) {
   178  		zipfile, err := CachePath(ctx, mod, "zip")
   179  		if err != nil {
   180  			return "", err
   181  		}
   182  		ziphashfile := zipfile + "hash"
   183  
   184  		// Return without locking if the zip and ziphash files exist.
   185  		if _, err := os.Stat(zipfile); err == nil {
   186  			if _, err := os.Stat(ziphashfile); err == nil {
   187  				return zipfile, nil
   188  			}
   189  		}
   190  
   191  		// The zip or ziphash file does not exist. Acquire the lock and create them.
   192  		if cfg.CmdName != "mod download" {
   193  			vers := mod.Version
   194  			if mod.Path == "golang.org/toolchain" {
   195  				// Shorten v0.0.1-go1.13.1.darwin-amd64 to go1.13.1.darwin-amd64
   196  				_, vers, _ = strings.Cut(vers, "-")
   197  				if i := strings.LastIndex(vers, "."); i >= 0 {
   198  					goos, goarch, _ := strings.Cut(vers[i+1:], "-")
   199  					vers = vers[:i] + " (" + goos + "/" + goarch + ")"
   200  				}
   201  				fmt.Fprintf(os.Stderr, "go: downloading %s\n", vers)
   202  			} else {
   203  				fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, vers)
   204  			}
   205  		}
   206  		unlock, err := lockVersion(ctx, mod)
   207  		if err != nil {
   208  			return "", err
   209  		}
   210  		defer unlock()
   211  
   212  		if err := downloadZip(ctx, mod, zipfile); err != nil {
   213  			return "", err
   214  		}
   215  		return zipfile, nil
   216  	})
   217  }
   218  
   219  func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) {
   220  	ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile)
   221  	defer span.Done()
   222  
   223  	// Double-check that the zipfile was not created while we were waiting for
   224  	// the lock in DownloadZip.
   225  	ziphashfile := zipfile + "hash"
   226  	var zipExists, ziphashExists bool
   227  	if _, err := os.Stat(zipfile); err == nil {
   228  		zipExists = true
   229  	}
   230  	if _, err := os.Stat(ziphashfile); err == nil {
   231  		ziphashExists = true
   232  	}
   233  	if zipExists && ziphashExists {
   234  		return nil
   235  	}
   236  
   237  	// Create parent directories.
   238  	if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
   239  		return err
   240  	}
   241  
   242  	// Clean up any remaining tempfiles from previous runs.
   243  	// This is only safe to do because the lock file ensures that their
   244  	// writers are no longer active.
   245  	tmpPattern := filepath.Base(zipfile) + "*.tmp"
   246  	if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(filepath.Dir(zipfile)), tmpPattern)); err == nil {
   247  		for _, path := range old {
   248  			os.Remove(path) // best effort
   249  		}
   250  	}
   251  
   252  	// If the zip file exists, the ziphash file must have been deleted
   253  	// or lost after a file system crash. Re-hash the zip without downloading.
   254  	if zipExists {
   255  		return hashZip(mod, zipfile, ziphashfile)
   256  	}
   257  
   258  	// From here to the os.Rename call below is functionally almost equivalent to
   259  	// renameio.WriteToFile, with one key difference: we want to validate the
   260  	// contents of the file (by hashing it) before we commit it. Because the file
   261  	// is zip-compressed, we need an actual file — or at least an io.ReaderAt — to
   262  	// validate it: we can't just tee the stream as we write it.
   263  	f, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0666)
   264  	if err != nil {
   265  		return err
   266  	}
   267  	defer func() {
   268  		if err != nil {
   269  			f.Close()
   270  			os.Remove(f.Name())
   271  		}
   272  	}()
   273  
   274  	var unrecoverableErr error
   275  	err = TryProxies(func(proxy string) error {
   276  		if unrecoverableErr != nil {
   277  			return unrecoverableErr
   278  		}
   279  		repo := Lookup(ctx, proxy, mod.Path)
   280  		err := repo.Zip(ctx, f, mod.Version)
   281  		if err != nil {
   282  			// Zip may have partially written to f before failing.
   283  			// (Perhaps the server crashed while sending the file?)
   284  			// Since we allow fallback on error in some cases, we need to fix up the
   285  			// file to be empty again for the next attempt.
   286  			if _, err := f.Seek(0, io.SeekStart); err != nil {
   287  				unrecoverableErr = err
   288  				return err
   289  			}
   290  			if err := f.Truncate(0); err != nil {
   291  				unrecoverableErr = err
   292  				return err
   293  			}
   294  		}
   295  		return err
   296  	})
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	// Double-check that the paths within the zip file are well-formed.
   302  	//
   303  	// TODO(bcmills): There is a similar check within the Unzip function. Can we eliminate one?
   304  	fi, err := f.Stat()
   305  	if err != nil {
   306  		return err
   307  	}
   308  	z, err := zip.NewReader(f, fi.Size())
   309  	if err != nil {
   310  		return err
   311  	}
   312  	prefix := mod.Path + "@" + mod.Version + "/"
   313  	for _, f := range z.File {
   314  		if !strings.HasPrefix(f.Name, prefix) {
   315  			return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name)
   316  		}
   317  	}
   318  
   319  	if err := f.Close(); err != nil {
   320  		return err
   321  	}
   322  
   323  	// Hash the zip file and check the sum before renaming to the final location.
   324  	if err := hashZip(mod, f.Name(), ziphashfile); err != nil {
   325  		return err
   326  	}
   327  	if err := os.Rename(f.Name(), zipfile); err != nil {
   328  		return err
   329  	}
   330  
   331  	// TODO(bcmills): Should we make the .zip and .ziphash files read-only to discourage tampering?
   332  
   333  	return nil
   334  }
   335  
   336  // hashZip reads the zip file opened in f, then writes the hash to ziphashfile,
   337  // overwriting that file if it exists.
   338  //
   339  // If the hash does not match go.sum (or the sumdb if enabled), hashZip returns
   340  // an error and does not write ziphashfile.
   341  func hashZip(mod module.Version, zipfile, ziphashfile string) (err error) {
   342  	hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
   343  	if err != nil {
   344  		return err
   345  	}
   346  	if err := checkModSum(mod, hash); err != nil {
   347  		return err
   348  	}
   349  	hf, err := lockedfile.Create(ziphashfile)
   350  	if err != nil {
   351  		return err
   352  	}
   353  	defer func() {
   354  		if closeErr := hf.Close(); err == nil && closeErr != nil {
   355  			err = closeErr
   356  		}
   357  	}()
   358  	if err := hf.Truncate(int64(len(hash))); err != nil {
   359  		return err
   360  	}
   361  	if _, err := hf.WriteAt([]byte(hash), 0); err != nil {
   362  		return err
   363  	}
   364  	return nil
   365  }
   366  
   367  // makeDirsReadOnly makes a best-effort attempt to remove write permissions for dir
   368  // and its transitive contents.
   369  func makeDirsReadOnly(dir string) {
   370  	type pathMode struct {
   371  		path string
   372  		mode fs.FileMode
   373  	}
   374  	var dirs []pathMode // in lexical order
   375  	filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
   376  		if err == nil && d.IsDir() {
   377  			info, err := d.Info()
   378  			if err == nil && info.Mode()&0222 != 0 {
   379  				dirs = append(dirs, pathMode{path, info.Mode()})
   380  			}
   381  		}
   382  		return nil
   383  	})
   384  
   385  	// Run over list backward to chmod children before parents.
   386  	for i := len(dirs) - 1; i >= 0; i-- {
   387  		os.Chmod(dirs[i].path, dirs[i].mode&^0222)
   388  	}
   389  }
   390  
   391  // RemoveAll removes a directory written by Download or Unzip, first applying
   392  // any permission changes needed to do so.
   393  func RemoveAll(dir string) error {
   394  	// Module cache has 0555 directories; make them writable in order to remove content.
   395  	filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
   396  		if err != nil {
   397  			return nil // ignore errors walking in file system
   398  		}
   399  		if info.IsDir() {
   400  			os.Chmod(path, 0777)
   401  		}
   402  		return nil
   403  	})
   404  	return robustio.RemoveAll(dir)
   405  }
   406  
   407  var GoSumFile string             // path to go.sum; set by package modload
   408  var WorkspaceGoSumFiles []string // path to module go.sums in workspace; set by package modload
   409  
   410  type modSum struct {
   411  	mod module.Version
   412  	sum string
   413  }
   414  
   415  var goSum struct {
   416  	mu        sync.Mutex
   417  	m         map[module.Version][]string            // content of go.sum file
   418  	w         map[string]map[module.Version][]string // sum file in workspace -> content of that sum file
   419  	status    map[modSum]modSumStatus                // state of sums in m
   420  	overwrite bool                                   // if true, overwrite go.sum without incorporating its contents
   421  	enabled   bool                                   // whether to use go.sum at all
   422  }
   423  
   424  type modSumStatus struct {
   425  	used, dirty bool
   426  }
   427  
   428  // Reset resets globals in the modfetch package, so previous loads don't affect
   429  // contents of go.sum files.
   430  func Reset() {
   431  	GoSumFile = ""
   432  	WorkspaceGoSumFiles = nil
   433  
   434  	// Uses of lookupCache and downloadCache both can call checkModSum,
   435  	// which in turn sets the used bit on goSum.status for modules.
   436  	// Reset them so used can be computed properly.
   437  	lookupCache = par.Cache[lookupCacheKey, Repo]{}
   438  	downloadCache = par.ErrCache[module.Version, string]{}
   439  
   440  	// Clear all fields on goSum. It will be initialized later
   441  	goSum.mu.Lock()
   442  	goSum.m = nil
   443  	goSum.w = nil
   444  	goSum.status = nil
   445  	goSum.overwrite = false
   446  	goSum.enabled = false
   447  	goSum.mu.Unlock()
   448  }
   449  
   450  // initGoSum initializes the go.sum data.
   451  // The boolean it returns reports whether the
   452  // use of go.sum is now enabled.
   453  // The goSum lock must be held.
   454  func initGoSum() (bool, error) {
   455  	if GoSumFile == "" {
   456  		return false, nil
   457  	}
   458  	if goSum.m != nil {
   459  		return true, nil
   460  	}
   461  
   462  	goSum.m = make(map[module.Version][]string)
   463  	goSum.status = make(map[modSum]modSumStatus)
   464  	goSum.w = make(map[string]map[module.Version][]string)
   465  
   466  	for _, f := range WorkspaceGoSumFiles {
   467  		goSum.w[f] = make(map[module.Version][]string)
   468  		_, err := readGoSumFile(goSum.w[f], f)
   469  		if err != nil {
   470  			return false, err
   471  		}
   472  	}
   473  
   474  	enabled, err := readGoSumFile(goSum.m, GoSumFile)
   475  	goSum.enabled = enabled
   476  	return enabled, err
   477  }
   478  
   479  func readGoSumFile(dst map[module.Version][]string, file string) (bool, error) {
   480  	var (
   481  		data []byte
   482  		err  error
   483  	)
   484  	if actualSumFile, ok := fsys.OverlayPath(file); ok {
   485  		// Don't lock go.sum if it's part of the overlay.
   486  		// On Plan 9, locking requires chmod, and we don't want to modify any file
   487  		// in the overlay. See #44700.
   488  		data, err = os.ReadFile(actualSumFile)
   489  	} else {
   490  		data, err = lockedfile.Read(file)
   491  	}
   492  	if err != nil && !os.IsNotExist(err) {
   493  		return false, err
   494  	}
   495  	readGoSum(dst, file, data)
   496  
   497  	return true, nil
   498  }
   499  
   500  // emptyGoModHash is the hash of a 1-file tree containing a 0-length go.mod.
   501  // A bug caused us to write these into go.sum files for non-modules.
   502  // We detect and remove them.
   503  const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="
   504  
   505  // readGoSum parses data, which is the content of file,
   506  // and adds it to goSum.m. The goSum lock must be held.
   507  func readGoSum(dst map[module.Version][]string, file string, data []byte) {
   508  	lineno := 0
   509  	for len(data) > 0 {
   510  		var line []byte
   511  		lineno++
   512  		i := bytes.IndexByte(data, '\n')
   513  		if i < 0 {
   514  			line, data = data, nil
   515  		} else {
   516  			line, data = data[:i], data[i+1:]
   517  		}
   518  		f := strings.Fields(string(line))
   519  		if len(f) == 0 {
   520  			// blank line; skip it
   521  			continue
   522  		}
   523  		if len(f) != 3 {
   524  			if cfg.CmdName == "mod tidy" {
   525  				// ignore malformed line so that go mod tidy can fix go.sum
   526  				continue
   527  			} else {
   528  				base.Fatalf("malformed go.sum:\n%s:%d: wrong number of fields %v\n", file, lineno, len(f))
   529  			}
   530  		}
   531  		if f[2] == emptyGoModHash {
   532  			// Old bug; drop it.
   533  			continue
   534  		}
   535  		mod := module.Version{Path: f[0], Version: f[1]}
   536  		dst[mod] = append(dst[mod], f[2])
   537  	}
   538  }
   539  
   540  // HaveSum returns true if the go.sum file contains an entry for mod.
   541  // The entry's hash must be generated with a known hash algorithm.
   542  // mod.Version may have a "/go.mod" suffix to distinguish sums for
   543  // .mod and .zip files.
   544  func HaveSum(mod module.Version) bool {
   545  	goSum.mu.Lock()
   546  	defer goSum.mu.Unlock()
   547  	inited, err := initGoSum()
   548  	if err != nil || !inited {
   549  		return false
   550  	}
   551  	for _, goSums := range goSum.w {
   552  		for _, h := range goSums[mod] {
   553  			if !strings.HasPrefix(h, "h1:") {
   554  				continue
   555  			}
   556  			if !goSum.status[modSum{mod, h}].dirty {
   557  				return true
   558  			}
   559  		}
   560  	}
   561  	for _, h := range goSum.m[mod] {
   562  		if !strings.HasPrefix(h, "h1:") {
   563  			continue
   564  		}
   565  		if !goSum.status[modSum{mod, h}].dirty {
   566  			return true
   567  		}
   568  	}
   569  	return false
   570  }
   571  
   572  // checkMod checks the given module's checksum and Go version.
   573  func checkMod(ctx context.Context, mod module.Version) {
   574  	// Do the file I/O before acquiring the go.sum lock.
   575  	ziphash, err := CachePath(ctx, mod, "ziphash")
   576  	if err != nil {
   577  		base.Fatalf("verifying %v", module.VersionError(mod, err))
   578  	}
   579  	data, err := lockedfile.Read(ziphash)
   580  	if err != nil {
   581  		base.Fatalf("verifying %v", module.VersionError(mod, err))
   582  	}
   583  	data = bytes.TrimSpace(data)
   584  	if !isValidSum(data) {
   585  		// Recreate ziphash file from zip file and use that to check the mod sum.
   586  		zip, err := CachePath(ctx, mod, "zip")
   587  		if err != nil {
   588  			base.Fatalf("verifying %v", module.VersionError(mod, err))
   589  		}
   590  		err = hashZip(mod, zip, ziphash)
   591  		if err != nil {
   592  			base.Fatalf("verifying %v", module.VersionError(mod, err))
   593  		}
   594  		return
   595  	}
   596  	h := string(data)
   597  	if !strings.HasPrefix(h, "h1:") {
   598  		base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h)))
   599  	}
   600  
   601  	if err := checkModSum(mod, h); err != nil {
   602  		base.Fatalf("%s", err)
   603  	}
   604  }
   605  
   606  // goModSum returns the checksum for the go.mod contents.
   607  func goModSum(data []byte) (string, error) {
   608  	return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
   609  		return io.NopCloser(bytes.NewReader(data)), nil
   610  	})
   611  }
   612  
   613  // checkGoMod checks the given module's go.mod checksum;
   614  // data is the go.mod content.
   615  func checkGoMod(path, version string, data []byte) error {
   616  	h, err := goModSum(data)
   617  	if err != nil {
   618  		return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)}
   619  	}
   620  
   621  	return checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
   622  }
   623  
   624  // checkModSum checks that the recorded checksum for mod is h.
   625  //
   626  // mod.Version may have the additional suffix "/go.mod" to request the checksum
   627  // for the module's go.mod file only.
   628  func checkModSum(mod module.Version, h string) error {
   629  	// We lock goSum when manipulating it,
   630  	// but we arrange to release the lock when calling checkSumDB,
   631  	// so that parallel calls to checkModHash can execute parallel calls
   632  	// to checkSumDB.
   633  
   634  	// Check whether mod+h is listed in go.sum already. If so, we're done.
   635  	goSum.mu.Lock()
   636  	inited, err := initGoSum()
   637  	if err != nil {
   638  		goSum.mu.Unlock()
   639  		return err
   640  	}
   641  	done := inited && haveModSumLocked(mod, h)
   642  	if inited {
   643  		st := goSum.status[modSum{mod, h}]
   644  		st.used = true
   645  		goSum.status[modSum{mod, h}] = st
   646  	}
   647  	goSum.mu.Unlock()
   648  
   649  	if done {
   650  		return nil
   651  	}
   652  
   653  	// Not listed, so we want to add them.
   654  	// Consult checksum database if appropriate.
   655  	if useSumDB(mod) {
   656  		// Calls base.Fatalf if mismatch detected.
   657  		if err := checkSumDB(mod, h); err != nil {
   658  			return err
   659  		}
   660  	}
   661  
   662  	// Add mod+h to go.sum, if it hasn't appeared already.
   663  	if inited {
   664  		goSum.mu.Lock()
   665  		addModSumLocked(mod, h)
   666  		st := goSum.status[modSum{mod, h}]
   667  		st.dirty = true
   668  		goSum.status[modSum{mod, h}] = st
   669  		goSum.mu.Unlock()
   670  	}
   671  	return nil
   672  }
   673  
   674  // haveModSumLocked reports whether the pair mod,h is already listed in go.sum.
   675  // If it finds a conflicting pair instead, it calls base.Fatalf.
   676  // goSum.mu must be locked.
   677  func haveModSumLocked(mod module.Version, h string) bool {
   678  	sumFileName := "go.sum"
   679  	if strings.HasSuffix(GoSumFile, "go.work.sum") {
   680  		sumFileName = "go.work.sum"
   681  	}
   682  	for _, vh := range goSum.m[mod] {
   683  		if h == vh {
   684  			return true
   685  		}
   686  		if strings.HasPrefix(vh, "h1:") {
   687  			base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s:     %v"+goSumMismatch, mod.Path, mod.Version, h, sumFileName, vh)
   688  		}
   689  	}
   690  	// Also check workspace sums.
   691  	foundMatch := false
   692  	// Check sums from all files in case there are conflicts between
   693  	// the files.
   694  	for goSumFile, goSums := range goSum.w {
   695  		for _, vh := range goSums[mod] {
   696  			if h == vh {
   697  				foundMatch = true
   698  			} else if strings.HasPrefix(vh, "h1:") {
   699  				base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s:     %v"+goSumMismatch, mod.Path, mod.Version, h, goSumFile, vh)
   700  			}
   701  		}
   702  	}
   703  	return foundMatch
   704  }
   705  
   706  // addModSumLocked adds the pair mod,h to go.sum.
   707  // goSum.mu must be locked.
   708  func addModSumLocked(mod module.Version, h string) {
   709  	if haveModSumLocked(mod, h) {
   710  		return
   711  	}
   712  	if len(goSum.m[mod]) > 0 {
   713  		fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h)
   714  	}
   715  	goSum.m[mod] = append(goSum.m[mod], h)
   716  }
   717  
   718  // checkSumDB checks the mod, h pair against the Go checksum database.
   719  // It calls base.Fatalf if the hash is to be rejected.
   720  func checkSumDB(mod module.Version, h string) error {
   721  	modWithoutSuffix := mod
   722  	noun := "module"
   723  	if before, found := strings.CutSuffix(mod.Version, "/go.mod"); found {
   724  		noun = "go.mod"
   725  		modWithoutSuffix.Version = before
   726  	}
   727  
   728  	db, lines, err := lookupSumDB(mod)
   729  	if err != nil {
   730  		return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: %v", noun, err))
   731  	}
   732  
   733  	have := mod.Path + " " + mod.Version + " " + h
   734  	prefix := mod.Path + " " + mod.Version + " h1:"
   735  	for _, line := range lines {
   736  		if line == have {
   737  			return nil
   738  		}
   739  		if strings.HasPrefix(line, prefix) {
   740  			return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+sumdbMismatch, noun, h, db, line[len(prefix)-len("h1:"):]))
   741  		}
   742  	}
   743  	return nil
   744  }
   745  
   746  // Sum returns the checksum for the downloaded copy of the given module,
   747  // if present in the download cache.
   748  func Sum(ctx context.Context, mod module.Version) string {
   749  	if cfg.GOMODCACHE == "" {
   750  		// Do not use current directory.
   751  		return ""
   752  	}
   753  
   754  	ziphash, err := CachePath(ctx, mod, "ziphash")
   755  	if err != nil {
   756  		return ""
   757  	}
   758  	data, err := lockedfile.Read(ziphash)
   759  	if err != nil {
   760  		return ""
   761  	}
   762  	data = bytes.TrimSpace(data)
   763  	if !isValidSum(data) {
   764  		return ""
   765  	}
   766  	return string(data)
   767  }
   768  
   769  // isValidSum returns true if data is the valid contents of a zip hash file.
   770  // Certain critical files are written to disk by first truncating
   771  // then writing the actual bytes, so that if the write fails
   772  // the corrupt file should contain at least one of the null
   773  // bytes written by the truncate operation.
   774  func isValidSum(data []byte) bool {
   775  	if bytes.IndexByte(data, '\000') >= 0 {
   776  		return false
   777  	}
   778  
   779  	if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) {
   780  		return false
   781  	}
   782  
   783  	return true
   784  }
   785  
   786  var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=readonly")
   787  
   788  // WriteGoSum writes the go.sum file if it needs to be updated.
   789  //
   790  // keep is used to check whether a newly added sum should be saved in go.sum.
   791  // It should have entries for both module content sums and go.mod sums
   792  // (version ends with "/go.mod"). Existing sums will be preserved unless they
   793  // have been marked for deletion with TrimGoSum.
   794  func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error {
   795  	goSum.mu.Lock()
   796  	defer goSum.mu.Unlock()
   797  
   798  	// If we haven't read the go.sum file yet, don't bother writing it.
   799  	if !goSum.enabled {
   800  		return nil
   801  	}
   802  
   803  	// Check whether we need to add sums for which keep[m] is true or remove
   804  	// unused sums marked with TrimGoSum. If there are no changes to make,
   805  	// just return without opening go.sum.
   806  	dirty := false
   807  Outer:
   808  	for m, hs := range goSum.m {
   809  		for _, h := range hs {
   810  			st := goSum.status[modSum{m, h}]
   811  			if st.dirty && (!st.used || keep[m]) {
   812  				dirty = true
   813  				break Outer
   814  			}
   815  		}
   816  	}
   817  	if !dirty {
   818  		return nil
   819  	}
   820  	if readonly {
   821  		return ErrGoSumDirty
   822  	}
   823  	if _, ok := fsys.OverlayPath(GoSumFile); ok {
   824  		base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
   825  	}
   826  
   827  	// Make a best-effort attempt to acquire the side lock, only to exclude
   828  	// previous versions of the 'go' command from making simultaneous edits.
   829  	if unlock, err := SideLock(ctx); err == nil {
   830  		defer unlock()
   831  	}
   832  
   833  	err := lockedfile.Transform(GoSumFile, func(data []byte) ([]byte, error) {
   834  		if !goSum.overwrite {
   835  			// Incorporate any sums added by other processes in the meantime.
   836  			// Add only the sums that we actually checked: the user may have edited or
   837  			// truncated the file to remove erroneous hashes, and we shouldn't restore
   838  			// them without good reason.
   839  			goSum.m = make(map[module.Version][]string, len(goSum.m))
   840  			readGoSum(goSum.m, GoSumFile, data)
   841  			for ms, st := range goSum.status {
   842  				if st.used && !sumInWorkspaceModulesLocked(ms.mod) {
   843  					addModSumLocked(ms.mod, ms.sum)
   844  				}
   845  			}
   846  		}
   847  
   848  		var mods []module.Version
   849  		for m := range goSum.m {
   850  			mods = append(mods, m)
   851  		}
   852  		module.Sort(mods)
   853  
   854  		var buf bytes.Buffer
   855  		for _, m := range mods {
   856  			list := goSum.m[m]
   857  			sort.Strings(list)
   858  			str.Uniq(&list)
   859  			for _, h := range list {
   860  				st := goSum.status[modSum{m, h}]
   861  				if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(m) {
   862  					fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
   863  				}
   864  			}
   865  		}
   866  		return buf.Bytes(), nil
   867  	})
   868  
   869  	if err != nil {
   870  		return fmt.Errorf("updating go.sum: %w", err)
   871  	}
   872  
   873  	goSum.status = make(map[modSum]modSumStatus)
   874  	goSum.overwrite = false
   875  	return nil
   876  }
   877  
   878  func sumInWorkspaceModulesLocked(m module.Version) bool {
   879  	for _, goSums := range goSum.w {
   880  		if _, ok := goSums[m]; ok {
   881  			return true
   882  		}
   883  	}
   884  	return false
   885  }
   886  
   887  // TrimGoSum trims go.sum to contain only the modules needed for reproducible
   888  // builds.
   889  //
   890  // keep is used to check whether a sum should be retained in go.mod. It should
   891  // have entries for both module content sums and go.mod sums (version ends
   892  // with "/go.mod").
   893  func TrimGoSum(keep map[module.Version]bool) {
   894  	goSum.mu.Lock()
   895  	defer goSum.mu.Unlock()
   896  	inited, err := initGoSum()
   897  	if err != nil {
   898  		base.Fatalf("%s", err)
   899  	}
   900  	if !inited {
   901  		return
   902  	}
   903  
   904  	for m, hs := range goSum.m {
   905  		if !keep[m] {
   906  			for _, h := range hs {
   907  				goSum.status[modSum{m, h}] = modSumStatus{used: false, dirty: true}
   908  			}
   909  			goSum.overwrite = true
   910  		}
   911  	}
   912  }
   913  
   914  const goSumMismatch = `
   915  
   916  SECURITY ERROR
   917  This download does NOT match an earlier download recorded in go.sum.
   918  The bits may have been replaced on the origin server, or an attacker may
   919  have intercepted the download attempt.
   920  
   921  For more information, see 'go help module-auth'.
   922  `
   923  
   924  const sumdbMismatch = `
   925  
   926  SECURITY ERROR
   927  This download does NOT match the one reported by the checksum server.
   928  The bits may have been replaced on the origin server, or an attacker may
   929  have intercepted the download attempt.
   930  
   931  For more information, see 'go help module-auth'.
   932  `
   933  
   934  const hashVersionMismatch = `
   935  
   936  SECURITY WARNING
   937  This download is listed in go.sum, but using an unknown hash algorithm.
   938  The download cannot be verified.
   939  
   940  For more information, see 'go help module-auth'.
   941  
   942  `
   943  
   944  var HelpModuleAuth = &base.Command{
   945  	UsageLine: "module-auth",
   946  	Short:     "module authentication using go.sum",
   947  	Long: `
   948  When the go command downloads a module zip file or go.mod file into the
   949  module cache, it computes a cryptographic hash and compares it with a known
   950  value to verify the file hasn't changed since it was first downloaded. Known
   951  hashes are stored in a file in the module root directory named go.sum. Hashes
   952  may also be downloaded from the checksum database depending on the values of
   953  GOSUMDB, GOPRIVATE, and GONOSUMDB.
   954  
   955  For details, see https://golang.org/ref/mod#authenticating.
   956  `,
   957  }
   958  
   959  var HelpPrivate = &base.Command{
   960  	UsageLine: "private",
   961  	Short:     "configuration for downloading non-public code",
   962  	Long: `
   963  The go command defaults to downloading modules from the public Go module
   964  mirror at proxy.golang.org. It also defaults to validating downloaded modules,
   965  regardless of source, against the public Go checksum database at sum.golang.org.
   966  These defaults work well for publicly available source code.
   967  
   968  The GOPRIVATE environment variable controls which modules the go command
   969  considers to be private (not available publicly) and should therefore not use
   970  the proxy or checksum database. The variable is a comma-separated list of
   971  glob patterns (in the syntax of Go's path.Match) of module path prefixes.
   972  For example,
   973  
   974  	GOPRIVATE=*.corp.example.com,rsc.io/private
   975  
   976  causes the go command to treat as private any module with a path prefix
   977  matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private,
   978  and rsc.io/private/quux.
   979  
   980  For fine-grained control over module download and validation, the GONOPROXY
   981  and GONOSUMDB environment variables accept the same kind of glob list
   982  and override GOPRIVATE for the specific decision of whether to use the proxy
   983  and checksum database, respectively.
   984  
   985  For example, if a company ran a module proxy serving private modules,
   986  users would configure go using:
   987  
   988  	GOPRIVATE=*.corp.example.com
   989  	GOPROXY=proxy.example.com
   990  	GONOPROXY=none
   991  
   992  The GOPRIVATE variable is also used to define the "public" and "private"
   993  patterns for the GOVCS variable; see 'go help vcs'. For that usage,
   994  GOPRIVATE applies even in GOPATH mode. In that case, it matches import paths
   995  instead of module paths.
   996  
   997  The 'go env -w' command (see 'go help env') can be used to set these variables
   998  for future go command invocations.
   999  
  1000  For more details, see https://golang.org/ref/mod#private-modules.
  1001  `,
  1002  }