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 5package modfetch 6 7import ( 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 "cmd/go/internal/base" 24 "cmd/go/internal/cfg" 25 "cmd/go/internal/fsys" 26 "cmd/go/internal/gover" 27 "cmd/go/internal/lockedfile" 28 "cmd/go/internal/par" 29 "cmd/go/internal/robustio" 30 "cmd/go/internal/str" 31 "cmd/go/internal/trace" 32 33 "golang.org/x/mod/module" 34 "golang.org/x/mod/sumdb/dirhash" 35 modzip "golang.org/x/mod/zip" 36) 37 38var downloadCache par.ErrCache[module.Version, string] // version → directory 39 40var 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. 45func 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 75func 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 171var 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. 175func 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 219func 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. 341func 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. 369func 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. 393func 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 407var GoSumFile string // path to go.sum; set by package modload 408var WorkspaceGoSumFiles []string // path to module go.sums in workspace; set by package modload 409 410type modSum struct { 411 mod module.Version 412 sum string 413} 414 415var 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 424type 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. 430func 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. 454func 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 479func 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. 503const 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. 507func 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. 544func 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// RecordedSum returns the sum if the go.sum file contains an entry for mod. 573// The boolean reports true if an entry was found or 574// false if no entry found or two conflicting sums are found. 575// The entry's hash must be generated with a known hash algorithm. 576// mod.Version may have a "/go.mod" suffix to distinguish sums for 577// .mod and .zip files. 578func RecordedSum(mod module.Version) (sum string, ok bool) { 579 goSum.mu.Lock() 580 defer goSum.mu.Unlock() 581 inited, err := initGoSum() 582 foundSum := "" 583 if err != nil || !inited { 584 return "", false 585 } 586 for _, goSums := range goSum.w { 587 for _, h := range goSums[mod] { 588 if !strings.HasPrefix(h, "h1:") { 589 continue 590 } 591 if !goSum.status[modSum{mod, h}].dirty { 592 if foundSum != "" && foundSum != h { // conflicting sums exist 593 return "", false 594 } 595 foundSum = h 596 } 597 } 598 } 599 for _, h := range goSum.m[mod] { 600 if !strings.HasPrefix(h, "h1:") { 601 continue 602 } 603 if !goSum.status[modSum{mod, h}].dirty { 604 if foundSum != "" && foundSum != h { // conflicting sums exist 605 return "", false 606 } 607 foundSum = h 608 } 609 } 610 return foundSum, true 611} 612 613// checkMod checks the given module's checksum and Go version. 614func checkMod(ctx context.Context, mod module.Version) { 615 // Do the file I/O before acquiring the go.sum lock. 616 ziphash, err := CachePath(ctx, mod, "ziphash") 617 if err != nil { 618 base.Fatalf("verifying %v", module.VersionError(mod, err)) 619 } 620 data, err := lockedfile.Read(ziphash) 621 if err != nil { 622 base.Fatalf("verifying %v", module.VersionError(mod, err)) 623 } 624 data = bytes.TrimSpace(data) 625 if !isValidSum(data) { 626 // Recreate ziphash file from zip file and use that to check the mod sum. 627 zip, err := CachePath(ctx, mod, "zip") 628 if err != nil { 629 base.Fatalf("verifying %v", module.VersionError(mod, err)) 630 } 631 err = hashZip(mod, zip, ziphash) 632 if err != nil { 633 base.Fatalf("verifying %v", module.VersionError(mod, err)) 634 } 635 return 636 } 637 h := string(data) 638 if !strings.HasPrefix(h, "h1:") { 639 base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h))) 640 } 641 642 if err := checkModSum(mod, h); err != nil { 643 base.Fatalf("%s", err) 644 } 645} 646 647// goModSum returns the checksum for the go.mod contents. 648func goModSum(data []byte) (string, error) { 649 return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) { 650 return io.NopCloser(bytes.NewReader(data)), nil 651 }) 652} 653 654// checkGoMod checks the given module's go.mod checksum; 655// data is the go.mod content. 656func checkGoMod(path, version string, data []byte) error { 657 h, err := goModSum(data) 658 if err != nil { 659 return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)} 660 } 661 662 return checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h) 663} 664 665// checkModSum checks that the recorded checksum for mod is h. 666// 667// mod.Version may have the additional suffix "/go.mod" to request the checksum 668// for the module's go.mod file only. 669func checkModSum(mod module.Version, h string) error { 670 // We lock goSum when manipulating it, 671 // but we arrange to release the lock when calling checkSumDB, 672 // so that parallel calls to checkModHash can execute parallel calls 673 // to checkSumDB. 674 675 // Check whether mod+h is listed in go.sum already. If so, we're done. 676 goSum.mu.Lock() 677 inited, err := initGoSum() 678 if err != nil { 679 goSum.mu.Unlock() 680 return err 681 } 682 done := inited && haveModSumLocked(mod, h) 683 if inited { 684 st := goSum.status[modSum{mod, h}] 685 st.used = true 686 goSum.status[modSum{mod, h}] = st 687 } 688 goSum.mu.Unlock() 689 690 if done { 691 return nil 692 } 693 694 // Not listed, so we want to add them. 695 // Consult checksum database if appropriate. 696 if useSumDB(mod) { 697 // Calls base.Fatalf if mismatch detected. 698 if err := checkSumDB(mod, h); err != nil { 699 return err 700 } 701 } 702 703 // Add mod+h to go.sum, if it hasn't appeared already. 704 if inited { 705 goSum.mu.Lock() 706 addModSumLocked(mod, h) 707 st := goSum.status[modSum{mod, h}] 708 st.dirty = true 709 goSum.status[modSum{mod, h}] = st 710 goSum.mu.Unlock() 711 } 712 return nil 713} 714 715// haveModSumLocked reports whether the pair mod,h is already listed in go.sum. 716// If it finds a conflicting pair instead, it calls base.Fatalf. 717// goSum.mu must be locked. 718func haveModSumLocked(mod module.Version, h string) bool { 719 sumFileName := "go.sum" 720 if strings.HasSuffix(GoSumFile, "go.work.sum") { 721 sumFileName = "go.work.sum" 722 } 723 for _, vh := range goSum.m[mod] { 724 if h == vh { 725 return true 726 } 727 if strings.HasPrefix(vh, "h1:") { 728 base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+goSumMismatch, mod.Path, mod.Version, h, sumFileName, vh) 729 } 730 } 731 // Also check workspace sums. 732 foundMatch := false 733 // Check sums from all files in case there are conflicts between 734 // the files. 735 for goSumFile, goSums := range goSum.w { 736 for _, vh := range goSums[mod] { 737 if h == vh { 738 foundMatch = true 739 } else if strings.HasPrefix(vh, "h1:") { 740 base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+goSumMismatch, mod.Path, mod.Version, h, goSumFile, vh) 741 } 742 } 743 } 744 return foundMatch 745} 746 747// addModSumLocked adds the pair mod,h to go.sum. 748// goSum.mu must be locked. 749func addModSumLocked(mod module.Version, h string) { 750 if haveModSumLocked(mod, h) { 751 return 752 } 753 if len(goSum.m[mod]) > 0 { 754 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) 755 } 756 goSum.m[mod] = append(goSum.m[mod], h) 757} 758 759// checkSumDB checks the mod, h pair against the Go checksum database. 760// It calls base.Fatalf if the hash is to be rejected. 761func checkSumDB(mod module.Version, h string) error { 762 modWithoutSuffix := mod 763 noun := "module" 764 if before, found := strings.CutSuffix(mod.Version, "/go.mod"); found { 765 noun = "go.mod" 766 modWithoutSuffix.Version = before 767 } 768 769 db, lines, err := lookupSumDB(mod) 770 if err != nil { 771 return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: %v", noun, err)) 772 } 773 774 have := mod.Path + " " + mod.Version + " " + h 775 prefix := mod.Path + " " + mod.Version + " h1:" 776 for _, line := range lines { 777 if line == have { 778 return nil 779 } 780 if strings.HasPrefix(line, prefix) { 781 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:"):])) 782 } 783 } 784 return nil 785} 786 787// Sum returns the checksum for the downloaded copy of the given module, 788// if present in the download cache. 789func Sum(ctx context.Context, mod module.Version) string { 790 if cfg.GOMODCACHE == "" { 791 // Do not use current directory. 792 return "" 793 } 794 795 ziphash, err := CachePath(ctx, mod, "ziphash") 796 if err != nil { 797 return "" 798 } 799 data, err := lockedfile.Read(ziphash) 800 if err != nil { 801 return "" 802 } 803 data = bytes.TrimSpace(data) 804 if !isValidSum(data) { 805 return "" 806 } 807 return string(data) 808} 809 810// isValidSum returns true if data is the valid contents of a zip hash file. 811// Certain critical files are written to disk by first truncating 812// then writing the actual bytes, so that if the write fails 813// the corrupt file should contain at least one of the null 814// bytes written by the truncate operation. 815func isValidSum(data []byte) bool { 816 if bytes.IndexByte(data, '\000') >= 0 { 817 return false 818 } 819 820 if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) { 821 return false 822 } 823 824 return true 825} 826 827var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=readonly") 828 829// WriteGoSum writes the go.sum file if it needs to be updated. 830// 831// keep is used to check whether a newly added sum should be saved in go.sum. 832// It should have entries for both module content sums and go.mod sums 833// (version ends with "/go.mod"). Existing sums will be preserved unless they 834// have been marked for deletion with TrimGoSum. 835func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error { 836 goSum.mu.Lock() 837 defer goSum.mu.Unlock() 838 839 // If we haven't read the go.sum file yet, don't bother writing it. 840 if !goSum.enabled { 841 return nil 842 } 843 844 // Check whether we need to add sums for which keep[m] is true or remove 845 // unused sums marked with TrimGoSum. If there are no changes to make, 846 // just return without opening go.sum. 847 dirty := false 848Outer: 849 for m, hs := range goSum.m { 850 for _, h := range hs { 851 st := goSum.status[modSum{m, h}] 852 if st.dirty && (!st.used || keep[m]) { 853 dirty = true 854 break Outer 855 } 856 } 857 } 858 if !dirty { 859 return nil 860 } 861 if readonly { 862 return ErrGoSumDirty 863 } 864 if _, ok := fsys.OverlayPath(GoSumFile); ok { 865 base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay") 866 } 867 868 // Make a best-effort attempt to acquire the side lock, only to exclude 869 // previous versions of the 'go' command from making simultaneous edits. 870 if unlock, err := SideLock(ctx); err == nil { 871 defer unlock() 872 } 873 874 err := lockedfile.Transform(GoSumFile, func(data []byte) ([]byte, error) { 875 tidyGoSum := tidyGoSum(data, keep) 876 return tidyGoSum, nil 877 }) 878 879 if err != nil { 880 return fmt.Errorf("updating go.sum: %w", err) 881 } 882 883 goSum.status = make(map[modSum]modSumStatus) 884 goSum.overwrite = false 885 return nil 886} 887 888// TidyGoSum returns a tidy version of the go.sum file. 889// A missing go.sum file is treated as if empty. 890func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { 891 goSum.mu.Lock() 892 defer goSum.mu.Unlock() 893 before, err := lockedfile.Read(GoSumFile) 894 if err != nil && !errors.Is(err, fs.ErrNotExist) { 895 base.Fatalf("reading go.sum: %v", err) 896 } 897 after = tidyGoSum(before, keep) 898 return before, after 899} 900 901// tidyGoSum returns a tidy version of the go.sum file. 902// The goSum lock must be held. 903func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { 904 if !goSum.overwrite { 905 // Incorporate any sums added by other processes in the meantime. 906 // Add only the sums that we actually checked: the user may have edited or 907 // truncated the file to remove erroneous hashes, and we shouldn't restore 908 // them without good reason. 909 goSum.m = make(map[module.Version][]string, len(goSum.m)) 910 readGoSum(goSum.m, GoSumFile, data) 911 for ms, st := range goSum.status { 912 if st.used && !sumInWorkspaceModulesLocked(ms.mod) { 913 addModSumLocked(ms.mod, ms.sum) 914 } 915 } 916 } 917 918 var mods []module.Version 919 for m := range goSum.m { 920 mods = append(mods, m) 921 } 922 module.Sort(mods) 923 924 var buf bytes.Buffer 925 for _, m := range mods { 926 list := goSum.m[m] 927 sort.Strings(list) 928 str.Uniq(&list) 929 for _, h := range list { 930 st := goSum.status[modSum{m, h}] 931 if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(m) { 932 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) 933 } 934 } 935 } 936 return buf.Bytes() 937} 938 939func sumInWorkspaceModulesLocked(m module.Version) bool { 940 for _, goSums := range goSum.w { 941 if _, ok := goSums[m]; ok { 942 return true 943 } 944 } 945 return false 946} 947 948// TrimGoSum trims go.sum to contain only the modules needed for reproducible 949// builds. 950// 951// keep is used to check whether a sum should be retained in go.mod. It should 952// have entries for both module content sums and go.mod sums (version ends 953// with "/go.mod"). 954func TrimGoSum(keep map[module.Version]bool) { 955 goSum.mu.Lock() 956 defer goSum.mu.Unlock() 957 inited, err := initGoSum() 958 if err != nil { 959 base.Fatalf("%s", err) 960 } 961 if !inited { 962 return 963 } 964 965 for m, hs := range goSum.m { 966 if !keep[m] { 967 for _, h := range hs { 968 goSum.status[modSum{m, h}] = modSumStatus{used: false, dirty: true} 969 } 970 goSum.overwrite = true 971 } 972 } 973} 974 975const goSumMismatch = ` 976 977SECURITY ERROR 978This download does NOT match an earlier download recorded in go.sum. 979The bits may have been replaced on the origin server, or an attacker may 980have intercepted the download attempt. 981 982For more information, see 'go help module-auth'. 983` 984 985const sumdbMismatch = ` 986 987SECURITY ERROR 988This download does NOT match the one reported by the checksum server. 989The bits may have been replaced on the origin server, or an attacker may 990have intercepted the download attempt. 991 992For more information, see 'go help module-auth'. 993` 994 995const hashVersionMismatch = ` 996 997SECURITY WARNING 998This download is listed in go.sum, but using an unknown hash algorithm. 999The download cannot be verified. 1000 1001For more information, see 'go help module-auth'. 1002 1003` 1004 1005var HelpModuleAuth = &base.Command{ 1006 UsageLine: "module-auth", 1007 Short: "module authentication using go.sum", 1008 Long: ` 1009When the go command downloads a module zip file or go.mod file into the 1010module cache, it computes a cryptographic hash and compares it with a known 1011value to verify the file hasn't changed since it was first downloaded. Known 1012hashes are stored in a file in the module root directory named go.sum. Hashes 1013may also be downloaded from the checksum database depending on the values of 1014GOSUMDB, GOPRIVATE, and GONOSUMDB. 1015 1016For details, see https://golang.org/ref/mod#authenticating. 1017`, 1018} 1019 1020var HelpPrivate = &base.Command{ 1021 UsageLine: "private", 1022 Short: "configuration for downloading non-public code", 1023 Long: ` 1024The go command defaults to downloading modules from the public Go module 1025mirror at proxy.golang.org. It also defaults to validating downloaded modules, 1026regardless of source, against the public Go checksum database at sum.golang.org. 1027These defaults work well for publicly available source code. 1028 1029The GOPRIVATE environment variable controls which modules the go command 1030considers to be private (not available publicly) and should therefore not use 1031the proxy or checksum database. The variable is a comma-separated list of 1032glob patterns (in the syntax of Go's path.Match) of module path prefixes. 1033For example, 1034 1035 GOPRIVATE=*.corp.example.com,rsc.io/private 1036 1037causes the go command to treat as private any module with a path prefix 1038matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private, 1039and rsc.io/private/quux. 1040 1041For fine-grained control over module download and validation, the GONOPROXY 1042and GONOSUMDB environment variables accept the same kind of glob list 1043and override GOPRIVATE for the specific decision of whether to use the proxy 1044and checksum database, respectively. 1045 1046For example, if a company ran a module proxy serving private modules, 1047users would configure go using: 1048 1049 GOPRIVATE=*.corp.example.com 1050 GOPROXY=proxy.example.com 1051 GONOPROXY=none 1052 1053The GOPRIVATE variable is also used to define the "public" and "private" 1054patterns for the GOVCS variable; see 'go help vcs'. For that usage, 1055GOPRIVATE applies even in GOPATH mode. In that case, it matches import paths 1056instead of module paths. 1057 1058The 'go env -w' command (see 'go help env') can be used to set these variables 1059for future go command invocations. 1060 1061For more details, see https://golang.org/ref/mod#private-modules. 1062`, 1063} 1064