1
2
3
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 "cmd/go/internal/base"
24 "cmd/go/internal/cfg"
25 "cmd/go/internal/fsys"
26 "cmd/go/internal/lockedfile"
27 "cmd/go/internal/par"
28 "cmd/go/internal/robustio"
29 "cmd/go/internal/trace"
30
31 "golang.org/x/mod/module"
32 "golang.org/x/mod/sumdb/dirhash"
33 modzip "golang.org/x/mod/zip"
34 )
35
36 var downloadCache par.Cache
37
38
39
40
41 func Download(ctx context.Context, mod module.Version) (dir string, err error) {
42 if err := checkCacheDir(); err != nil {
43 base.Fatalf("go: %v", err)
44 }
45
46
47 type cached struct {
48 dir string
49 err error
50 }
51 c := downloadCache.Do(mod, func() interface{} {
52 dir, err := download(ctx, mod)
53 if err != nil {
54 return cached{"", err}
55 }
56 checkMod(mod)
57 return cached{dir, nil}
58 }).(cached)
59 return c.dir, c.err
60 }
61
62 func download(ctx context.Context, mod module.Version) (dir string, err error) {
63 ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
64 defer span.Done()
65
66 dir, err = DownloadDir(mod)
67 if err == nil {
68
69 return dir, nil
70 } else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
71 return "", err
72 }
73
74
75
76
77 zipfile, err := DownloadZip(ctx, mod)
78 if err != nil {
79 return "", err
80 }
81
82 unlock, err := lockVersion(mod)
83 if err != nil {
84 return "", err
85 }
86 defer unlock()
87
88 ctx, span = trace.StartSpan(ctx, "unzip "+zipfile)
89 defer span.Done()
90
91
92 _, dirErr := DownloadDir(mod)
93 if dirErr == nil {
94 return dir, nil
95 }
96 _, dirExists := dirErr.(*DownloadDirPartialError)
97
98
99
100
101
102
103 parentDir := filepath.Dir(dir)
104 tmpPrefix := filepath.Base(dir) + ".tmp-"
105 if old, err := filepath.Glob(filepath.Join(parentDir, tmpPrefix+"*")); err == nil {
106 for _, path := range old {
107 RemoveAll(path)
108 }
109 }
110 if dirExists {
111 if err := RemoveAll(dir); err != nil {
112 return "", err
113 }
114 }
115
116 partialPath, err := CachePath(mod, "partial")
117 if err != nil {
118 return "", err
119 }
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 if err := os.MkdirAll(parentDir, 0777); err != nil {
136 return "", err
137 }
138 if err := os.WriteFile(partialPath, nil, 0666); err != nil {
139 return "", err
140 }
141 if err := modzip.Unzip(dir, mod, zipfile); err != nil {
142 fmt.Fprintf(os.Stderr, "-> %s\n", err)
143 if rmErr := RemoveAll(dir); rmErr == nil {
144 os.Remove(partialPath)
145 }
146 return "", err
147 }
148 if err := os.Remove(partialPath); err != nil {
149 return "", err
150 }
151
152 if !cfg.ModCacheRW {
153 makeDirsReadOnly(dir)
154 }
155 return dir, nil
156 }
157
158 var downloadZipCache par.Cache
159
160
161
162 func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
163
164 type cached struct {
165 zipfile string
166 err error
167 }
168 c := downloadZipCache.Do(mod, func() interface{} {
169 zipfile, err := CachePath(mod, "zip")
170 if err != nil {
171 return cached{"", err}
172 }
173 ziphashfile := zipfile + "hash"
174
175
176 if _, err := os.Stat(zipfile); err == nil {
177 if _, err := os.Stat(ziphashfile); err == nil {
178 return cached{zipfile, nil}
179 }
180 }
181
182
183 if cfg.CmdName != "mod download" {
184 fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version)
185 }
186 unlock, err := lockVersion(mod)
187 if err != nil {
188 return cached{"", err}
189 }
190 defer unlock()
191
192 if err := downloadZip(ctx, mod, zipfile); err != nil {
193 return cached{"", err}
194 }
195 return cached{zipfile, nil}
196 }).(cached)
197 return c.zipfile, c.err
198 }
199
200 func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) {
201 ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile)
202 defer span.Done()
203
204
205
206 ziphashfile := zipfile + "hash"
207 var zipExists, ziphashExists bool
208 if _, err := os.Stat(zipfile); err == nil {
209 zipExists = true
210 }
211 if _, err := os.Stat(ziphashfile); err == nil {
212 ziphashExists = true
213 }
214 if zipExists && ziphashExists {
215 return nil
216 }
217
218
219 if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
220 return err
221 }
222
223
224
225
226 tmpPattern := filepath.Base(zipfile) + "*.tmp"
227 if old, err := filepath.Glob(filepath.Join(filepath.Dir(zipfile), tmpPattern)); err == nil {
228 for _, path := range old {
229 os.Remove(path)
230 }
231 }
232
233
234
235 if zipExists {
236 return hashZip(mod, zipfile, ziphashfile)
237 }
238
239
240
241
242
243
244 f, err := os.CreateTemp(filepath.Dir(zipfile), tmpPattern)
245 if err != nil {
246 return err
247 }
248 defer func() {
249 if err != nil {
250 f.Close()
251 os.Remove(f.Name())
252 }
253 }()
254
255 var unrecoverableErr error
256 err = TryProxies(func(proxy string) error {
257 if unrecoverableErr != nil {
258 return unrecoverableErr
259 }
260 repo := Lookup(proxy, mod.Path)
261 err := repo.Zip(f, mod.Version)
262 if err != nil {
263
264
265
266
267 if _, err := f.Seek(0, io.SeekStart); err != nil {
268 unrecoverableErr = err
269 return err
270 }
271 if err := f.Truncate(0); err != nil {
272 unrecoverableErr = err
273 return err
274 }
275 }
276 return err
277 })
278 if err != nil {
279 return err
280 }
281
282
283
284
285 fi, err := f.Stat()
286 if err != nil {
287 return err
288 }
289 z, err := zip.NewReader(f, fi.Size())
290 if err != nil {
291 return err
292 }
293 prefix := mod.Path + "@" + mod.Version + "/"
294 for _, f := range z.File {
295 if !strings.HasPrefix(f.Name, prefix) {
296 return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name)
297 }
298 }
299
300 if err := f.Close(); err != nil {
301 return err
302 }
303
304
305 if err := hashZip(mod, f.Name(), ziphashfile); err != nil {
306 return err
307 }
308 if err := os.Rename(f.Name(), zipfile); err != nil {
309 return err
310 }
311
312
313
314 return nil
315 }
316
317
318
319
320
321
322 func hashZip(mod module.Version, zipfile, ziphashfile string) error {
323 hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
324 if err != nil {
325 return err
326 }
327 if err := checkModSum(mod, hash); err != nil {
328 return err
329 }
330 hf, err := lockedfile.Create(ziphashfile)
331 if err != nil {
332 return err
333 }
334 if err := hf.Truncate(int64(len(hash))); err != nil {
335 return err
336 }
337 if _, err := hf.WriteAt([]byte(hash), 0); err != nil {
338 return err
339 }
340 if err := hf.Close(); err != nil {
341 return err
342 }
343
344 return nil
345 }
346
347
348
349 func makeDirsReadOnly(dir string) {
350 type pathMode struct {
351 path string
352 mode fs.FileMode
353 }
354 var dirs []pathMode
355 filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
356 if err == nil && d.IsDir() {
357 info, err := d.Info()
358 if err == nil && info.Mode()&0222 != 0 {
359 dirs = append(dirs, pathMode{path, info.Mode()})
360 }
361 }
362 return nil
363 })
364
365
366 for i := len(dirs) - 1; i >= 0; i-- {
367 os.Chmod(dirs[i].path, dirs[i].mode&^0222)
368 }
369 }
370
371
372
373 func RemoveAll(dir string) error {
374
375 filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
376 if err != nil {
377 return nil
378 }
379 if info.IsDir() {
380 os.Chmod(path, 0777)
381 }
382 return nil
383 })
384 return robustio.RemoveAll(dir)
385 }
386
387 var GoSumFile string
388
389 type modSum struct {
390 mod module.Version
391 sum string
392 }
393
394 var goSum struct {
395 mu sync.Mutex
396 m map[module.Version][]string
397 status map[modSum]modSumStatus
398 overwrite bool
399 enabled bool
400 }
401
402 type modSumStatus struct {
403 used, dirty bool
404 }
405
406
407
408
409
410 func initGoSum() (bool, error) {
411 if GoSumFile == "" {
412 return false, nil
413 }
414 if goSum.m != nil {
415 return true, nil
416 }
417
418 goSum.m = make(map[module.Version][]string)
419 goSum.status = make(map[modSum]modSumStatus)
420 var (
421 data []byte
422 err error
423 )
424 if actualSumFile, ok := fsys.OverlayPath(GoSumFile); ok {
425
426
427
428 data, err = os.ReadFile(actualSumFile)
429 } else {
430 data, err = lockedfile.Read(GoSumFile)
431 }
432 if err != nil && !os.IsNotExist(err) {
433 return false, err
434 }
435 goSum.enabled = true
436 readGoSum(goSum.m, GoSumFile, data)
437
438 return true, nil
439 }
440
441
442
443
444 const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="
445
446
447
448 func readGoSum(dst map[module.Version][]string, file string, data []byte) error {
449 lineno := 0
450 for len(data) > 0 {
451 var line []byte
452 lineno++
453 i := bytes.IndexByte(data, '\n')
454 if i < 0 {
455 line, data = data, nil
456 } else {
457 line, data = data[:i], data[i+1:]
458 }
459 f := strings.Fields(string(line))
460 if len(f) == 0 {
461
462 continue
463 }
464 if len(f) != 3 {
465 return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
466 }
467 if f[2] == emptyGoModHash {
468
469 continue
470 }
471 mod := module.Version{Path: f[0], Version: f[1]}
472 dst[mod] = append(dst[mod], f[2])
473 }
474 return nil
475 }
476
477
478
479
480
481 func HaveSum(mod module.Version) bool {
482 goSum.mu.Lock()
483 defer goSum.mu.Unlock()
484 inited, err := initGoSum()
485 if err != nil || !inited {
486 return false
487 }
488 for _, h := range goSum.m[mod] {
489 if !strings.HasPrefix(h, "h1:") {
490 continue
491 }
492 if !goSum.status[modSum{mod, h}].dirty {
493 return true
494 }
495 }
496 return false
497 }
498
499
500 func checkMod(mod module.Version) {
501
502 ziphash, err := CachePath(mod, "ziphash")
503 if err != nil {
504 base.Fatalf("verifying %v", module.VersionError(mod, err))
505 }
506 data, err := lockedfile.Read(ziphash)
507 if err != nil {
508 base.Fatalf("verifying %v", module.VersionError(mod, err))
509 }
510 data = bytes.TrimSpace(data)
511 if !isValidSum(data) {
512
513 zip, err := CachePath(mod, "zip")
514 if err != nil {
515 base.Fatalf("verifying %v", module.VersionError(mod, err))
516 }
517 err = hashZip(mod, zip, ziphash)
518 if err != nil {
519 base.Fatalf("verifying %v", module.VersionError(mod, err))
520 }
521 return
522 }
523 h := string(data)
524 if !strings.HasPrefix(h, "h1:") {
525 base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h)))
526 }
527
528 if err := checkModSum(mod, h); err != nil {
529 base.Fatalf("%s", err)
530 }
531 }
532
533
534 func goModSum(data []byte) (string, error) {
535 return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
536 return io.NopCloser(bytes.NewReader(data)), nil
537 })
538 }
539
540
541
542 func checkGoMod(path, version string, data []byte) error {
543 h, err := goModSum(data)
544 if err != nil {
545 return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)}
546 }
547
548 return checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
549 }
550
551
552
553
554
555 func checkModSum(mod module.Version, h string) error {
556
557
558
559
560
561
562 goSum.mu.Lock()
563 inited, err := initGoSum()
564 if err != nil {
565 goSum.mu.Unlock()
566 return err
567 }
568 done := inited && haveModSumLocked(mod, h)
569 if inited {
570 st := goSum.status[modSum{mod, h}]
571 st.used = true
572 goSum.status[modSum{mod, h}] = st
573 }
574 goSum.mu.Unlock()
575
576 if done {
577 return nil
578 }
579
580
581
582 if useSumDB(mod) {
583
584 if err := checkSumDB(mod, h); err != nil {
585 return err
586 }
587 }
588
589
590 if inited {
591 goSum.mu.Lock()
592 addModSumLocked(mod, h)
593 st := goSum.status[modSum{mod, h}]
594 st.dirty = true
595 goSum.status[modSum{mod, h}] = st
596 goSum.mu.Unlock()
597 }
598 return nil
599 }
600
601
602
603
604 func haveModSumLocked(mod module.Version, h string) bool {
605 for _, vh := range goSum.m[mod] {
606 if h == vh {
607 return true
608 }
609 if strings.HasPrefix(vh, "h1:") {
610 base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum: %v"+goSumMismatch, mod.Path, mod.Version, h, vh)
611 }
612 }
613 return false
614 }
615
616
617
618 func addModSumLocked(mod module.Version, h string) {
619 if haveModSumLocked(mod, h) {
620 return
621 }
622 if len(goSum.m[mod]) > 0 {
623 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)
624 }
625 goSum.m[mod] = append(goSum.m[mod], h)
626 }
627
628
629
630 func checkSumDB(mod module.Version, h string) error {
631 modWithoutSuffix := mod
632 noun := "module"
633 if strings.HasSuffix(mod.Version, "/go.mod") {
634 noun = "go.mod"
635 modWithoutSuffix.Version = strings.TrimSuffix(mod.Version, "/go.mod")
636 }
637
638 db, lines, err := lookupSumDB(mod)
639 if err != nil {
640 return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: %v", noun, err))
641 }
642
643 have := mod.Path + " " + mod.Version + " " + h
644 prefix := mod.Path + " " + mod.Version + " h1:"
645 for _, line := range lines {
646 if line == have {
647 return nil
648 }
649 if strings.HasPrefix(line, prefix) {
650 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:"):]))
651 }
652 }
653 return nil
654 }
655
656
657
658 func Sum(mod module.Version) string {
659 if cfg.GOMODCACHE == "" {
660
661 return ""
662 }
663
664 ziphash, err := CachePath(mod, "ziphash")
665 if err != nil {
666 return ""
667 }
668 data, err := lockedfile.Read(ziphash)
669 if err != nil {
670 return ""
671 }
672 data = bytes.TrimSpace(data)
673 if !isValidSum(data) {
674 return ""
675 }
676 return string(data)
677 }
678
679
680
681
682
683
684 func isValidSum(data []byte) bool {
685 if bytes.IndexByte(data, '\000') >= 0 {
686 return false
687 }
688
689 if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) {
690 return false
691 }
692
693 return true
694 }
695
696
697
698
699
700
701
702 func WriteGoSum(keep map[module.Version]bool) {
703 goSum.mu.Lock()
704 defer goSum.mu.Unlock()
705
706
707 if !goSum.enabled {
708 return
709 }
710
711
712
713
714 dirty := false
715 Outer:
716 for m, hs := range goSum.m {
717 for _, h := range hs {
718 st := goSum.status[modSum{m, h}]
719 if st.dirty && (!st.used || keep[m]) {
720 dirty = true
721 break Outer
722 }
723 }
724 }
725 if !dirty {
726 return
727 }
728 if cfg.BuildMod == "readonly" {
729 base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly")
730 }
731 if _, ok := fsys.OverlayPath(GoSumFile); ok {
732 base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
733 }
734
735
736
737 if unlock, err := SideLock(); err == nil {
738 defer unlock()
739 }
740
741 err := lockedfile.Transform(GoSumFile, func(data []byte) ([]byte, error) {
742 if !goSum.overwrite {
743
744
745
746
747 goSum.m = make(map[module.Version][]string, len(goSum.m))
748 readGoSum(goSum.m, GoSumFile, data)
749 for ms, st := range goSum.status {
750 if st.used {
751 addModSumLocked(ms.mod, ms.sum)
752 }
753 }
754 }
755
756 var mods []module.Version
757 for m := range goSum.m {
758 mods = append(mods, m)
759 }
760 module.Sort(mods)
761
762 var buf bytes.Buffer
763 for _, m := range mods {
764 list := goSum.m[m]
765 sort.Strings(list)
766 for _, h := range list {
767 st := goSum.status[modSum{m, h}]
768 if !st.dirty || (st.used && keep[m]) {
769 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
770 }
771 }
772 }
773 return buf.Bytes(), nil
774 })
775
776 if err != nil {
777 base.Fatalf("go: updating go.sum: %v", err)
778 }
779
780 goSum.status = make(map[modSum]modSumStatus)
781 goSum.overwrite = false
782 }
783
784
785
786
787
788
789
790 func TrimGoSum(keep map[module.Version]bool) {
791 goSum.mu.Lock()
792 defer goSum.mu.Unlock()
793 inited, err := initGoSum()
794 if err != nil {
795 base.Fatalf("%s", err)
796 }
797 if !inited {
798 return
799 }
800
801 for m, hs := range goSum.m {
802 if !keep[m] {
803 for _, h := range hs {
804 goSum.status[modSum{m, h}] = modSumStatus{used: false, dirty: true}
805 }
806 goSum.overwrite = true
807 }
808 }
809 }
810
811 const goSumMismatch = `
812
813 SECURITY ERROR
814 This download does NOT match an earlier download recorded in go.sum.
815 The bits may have been replaced on the origin server, or an attacker may
816 have intercepted the download attempt.
817
818 For more information, see 'go help module-auth'.
819 `
820
821 const sumdbMismatch = `
822
823 SECURITY ERROR
824 This download does NOT match the one reported by the checksum server.
825 The bits may have been replaced on the origin server, or an attacker may
826 have intercepted the download attempt.
827
828 For more information, see 'go help module-auth'.
829 `
830
831 const hashVersionMismatch = `
832
833 SECURITY WARNING
834 This download is listed in go.sum, but using an unknown hash algorithm.
835 The download cannot be verified.
836
837 For more information, see 'go help module-auth'.
838
839 `
840
841 var HelpModuleAuth = &base.Command{
842 UsageLine: "module-auth",
843 Short: "module authentication using go.sum",
844 Long: `
845 When the go command downloads a module zip file or go.mod file into the
846 module cache, it computes a cryptographic hash and compares it with a known
847 value to verify the file hasn't changed since it was first downloaded. Known
848 hashes are stored in a file in the module root directory named go.sum. Hashes
849 may also be downloaded from the checksum database depending on the values of
850 GOSUMDB, GOPRIVATE, and GONOSUMDB.
851
852 For details, see https://golang.org/ref/mod#authenticating.
853 `,
854 }
855
856 var HelpPrivate = &base.Command{
857 UsageLine: "private",
858 Short: "configuration for downloading non-public code",
859 Long: `
860 The go command defaults to downloading modules from the public Go module
861 mirror at proxy.golang.org. It also defaults to validating downloaded modules,
862 regardless of source, against the public Go checksum database at sum.golang.org.
863 These defaults work well for publicly available source code.
864
865 The GOPRIVATE environment variable controls which modules the go command
866 considers to be private (not available publicly) and should therefore not use
867 the proxy or checksum database. The variable is a comma-separated list of
868 glob patterns (in the syntax of Go's path.Match) of module path prefixes.
869 For example,
870
871 GOPRIVATE=*.corp.example.com,rsc.io/private
872
873 causes the go command to treat as private any module with a path prefix
874 matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private,
875 and rsc.io/private/quux.
876
877 For fine-grained control over module download and validation, the GONOPROXY
878 and GONOSUMDB environment variables accept the same kind of glob list
879 and override GOPRIVATE for the specific decision of whether to use the proxy
880 and checksum database, respectively.
881
882 For example, if a company ran a module proxy serving private modules,
883 users would configure go using:
884
885 GOPRIVATE=*.corp.example.com
886 GOPROXY=proxy.example.com
887 GONOPROXY=none
888
889 The GOPRIVATE variable is also used to define the "public" and "private"
890 patterns for the GOVCS variable; see 'go help vcs'. For that usage,
891 GOPRIVATE applies even in GOPATH mode. In that case, it matches import paths
892 instead of module paths.
893
894 The 'go env -w' command (see 'go help env') can be used to set these variables
895 for future go command invocations.
896
897 For more details, see https://golang.org/ref/mod#private-modules.
898 `,
899 }
900
View as plain text