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