From 23e1d36a87f55b7e37d969ba3de3a8e1b8147f49 Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Thu, 1 Apr 2021 11:01:19 -0400 Subject: [PATCH] cmd/go: in 'go list -m', ignore "not found" errors loading updates "Not found" and "no matching version" errors usually indicate the user is offline or the proxy doesn't have a version of go.mod that could provide retractions. 'go list -m -u' should still succeed. We should still report unclassified errors though. Previously, we reported most errors loading retractions but did not report errors loading updates. This change makes those operations more consistent. Fixes #45305 Change-Id: I2f23a566c9481bc7ff229a177f39d78f6a8aae77 Reviewed-on: https://go-review.googlesource.com/c/go/+/306572 Trust: Jay Conrod Reviewed-by: Bryan C. Mills Reviewed-by: Michael Matloob --- src/cmd/go/internal/modload/build.go | 44 +++++++++++++-- .../go/testdata/script/mod_list_retract.txt | 12 ++-- .../script/mod_list_update_nolatest.txt | 55 +++++++++++++++++++ .../testdata/script/mod_retract_replace.txt | 6 +- 4 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 src/cmd/go/testdata/script/mod_list_update_nolatest.txt diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index 804bd5ecfd9d5..b8825408d7ad9 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "internal/goroot" + "io/fs" "os" "path/filepath" "strings" @@ -108,7 +109,26 @@ func addUpdate(ctx context.Context, m *modinfo.ModulePublic) { return } - if info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed); err == nil && semver.Compare(info.Version, m.Version) > 0 { + info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed) + var noVersionErr *NoMatchingVersionError + if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { + // Ignore "not found" and "no matching version" errors. This usually means + // the user is offline or the proxy doesn't have a matching version. + // + // We should report other errors though. An attacker that controls the + // network shouldn't be able to hide versions by interfering with + // the HTTPS connection. An attacker that controls the proxy may still + // hide versions, since the "list" and "latest" endpoints are not + // authenticated. + return + } else if err != nil { + if m.Error == nil { + m.Error = &modinfo.ModuleError{Err: err.Error()} + } + return + } + + if semver.Compare(info.Version, m.Version) > 0 { m.Update = &modinfo.ModulePublic{ Path: m.Path, Version: info.Version, @@ -140,14 +160,26 @@ func addRetraction(ctx context.Context, m *modinfo.ModulePublic) { } err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version}) - var rerr *ModuleRetractedError - if errors.As(err, &rerr) { - if len(rerr.Rationale) == 0 { + var noVersionErr *NoMatchingVersionError + var retractErr *ModuleRetractedError + if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { + // Ignore "not found" and "no matching version" errors. This usually means + // the user is offline or the proxy doesn't have a go.mod file that could + // contain retractions. + // + // We should report other errors though. An attacker that controls the + // network shouldn't be able to hide versions by interfering with + // the HTTPS connection. An attacker that controls the proxy may still + // hide versions, since the "list" and "latest" endpoints are not + // authenticated. + return + } else if errors.As(err, &retractErr) { + if len(retractErr.Rationale) == 0 { m.Retracted = []string{"retracted by module author"} } else { - m.Retracted = rerr.Rationale + m.Retracted = retractErr.Rationale } - } else if err != nil && m.Error == nil { + } else if m.Error == nil { m.Error = &modinfo.ModuleError{Err: err.Error()} } } diff --git a/src/cmd/go/testdata/script/mod_list_retract.txt b/src/cmd/go/testdata/script/mod_list_retract.txt index 3ba53bc59691c..4b133485152da 100644 --- a/src/cmd/go/testdata/script/mod_list_retract.txt +++ b/src/cmd/go/testdata/script/mod_list_retract.txt @@ -29,12 +29,12 @@ go list -m -retracted -f '{{with .Retracted}}retracted{{end}}' example.com/retra go list -m -f '{{with .Retracted}}retracted{{end}}' example.com/retract@v1.0.0-unused ! stdout . -# 'go list -m -retracted mod@version' shows an error if the go.mod that should -# contain the retractions is not available. -! go list -m -retracted example.com/retract/missingmod@v1.0.0 -stderr '^go list -m: loading module retractions for example.com/retract/missingmod@v1.0.0: .*404 Not Found$' -go list -e -m -retracted -f '{{.Error.Err}}' example.com/retract/missingmod@v1.0.0 -stdout '^loading module retractions for example.com/retract/missingmod@v1.0.0: .*404 Not Found$' +# 'go list -m -retracted mod@version' does not show an error if the module +# that would contain the retraction is unavailable. See #45305. +go list -m -retracted -f '{{.Path}} {{.Version}} {{.Error}}' example.com/retract/missingmod@v1.0.0 +stdout '^example.com/retract/missingmod v1.0.0 $' +exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.info +! exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.mod # 'go list -m -retracted mod@version' shows retractions. go list -m -retracted example.com/retract@v1.0.0-unused diff --git a/src/cmd/go/testdata/script/mod_list_update_nolatest.txt b/src/cmd/go/testdata/script/mod_list_update_nolatest.txt new file mode 100644 index 0000000000000..6d00f8ce1e557 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_list_update_nolatest.txt @@ -0,0 +1,55 @@ +# Check that if a proxy does not have a version of a module that could be +# an upgrade, 'go list -m -u' still succeeds. +# We use a local file proxy, since our test proxy doesn't have the behavior +# we want to test, and we don't want it to be too clever. +# Verifies #45305, where proxy.golang.org serves an empty /@v/list (200) +# but has no /@latest (410) because the go.mod at the tip of the default +# branch has a different major version suffix. +env testproxy=$GOPROXY +env GOPROXY=file://$WORK/proxy +env GOSUMDB=off + +# If the proxy does not return a list of versions (404/410) +# or a latest version (404/410), we should see no error. +go list -m example.com/noversion +stdout '^example.com/noversion v0.0.0$' +go list -m -u example.com/noversion +stdout '^example.com/noversion v0.0.0$' + +# If the proxy returns an empty list of versions (200, not 404/410) +# but does not have a latest version (404/410), we should see no error. +go list -m example.com/nolatest +stdout '^example.com/nolatest v0.0.0$' +go list -m -u example.com/nolatest +stdout '^example.com/nolatest v0.0.0$' + +# If proxy returns an invalid response, we should see an error. +env GOPROXY=$testproxy/invalid +! go list -m -u example.com/nolatest +stderr '^go list -m: loading module retractions for example.com/nolatest@v0.0.0: invalid response from proxy "[^"]*": invalid character ''i'' looking for beginning of value$' + +-- go.mod -- +module m + +go 1.17 + +require ( + example.com/nolatest v0.0.0 + example.com/noversion v0.0.0 +) +-- go.sum -- +example.com/nolatest v0.0.0/go.mod h1:HnLrCt6SJga5tCtJ7IzG9dOOCniY3G5C0VT7jfMdS0M= +example.com/noversion v0.0.0/go.mod h1:2RUfWiCYsygSXPM2Igxx0FD3Kq33OnVdxm34eDDhXbQ= +-- $WORK/proxy/example.com/nolatest/@v/list -- +-- $WORK/proxy/example.com/nolatest/@v/v0.0.0.info -- +{"Version":"v0.0.0"} +-- $WORK/proxy/example.com/nolatest/@v/v0.0.0.mod -- +module example.com/nolatest + +go 1.17 +-- $WORK/proxy/example.com/noversion/@v/v0.0.0.info -- +{"Version":"v0.0.0"} +-- $WORK/proxy/example.com/noversion/@v/v0.0.0.mod -- +module example.com/noversion + +go 1.17 diff --git a/src/cmd/go/testdata/script/mod_retract_replace.txt b/src/cmd/go/testdata/script/mod_retract_replace.txt index 770aea41a593b..9cd714739abf5 100644 --- a/src/cmd/go/testdata/script/mod_retract_replace.txt +++ b/src/cmd/go/testdata/script/mod_retract_replace.txt @@ -5,8 +5,10 @@ go get -d # The latest version, v1.9.0, is not available on the proxy. -! go list -m -retracted example.com/retract/missingmod -stderr '^go list -m: loading module retractions for example.com/retract/missingmod@v1.0.0: .*404 Not Found$' +go list -m -retracted example.com/retract/missingmod +stdout '^example.com/retract/missingmod v1.0.0$' +exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.info +! exists $GOPATH/pkg/mod/cache/download/example.com/retract/missingmod/@v/v1.9.0.mod # If we replace that version, we should see retractions. go mod edit -replace=example.com/retract/missingmod@v1.9.0=./missingmod-v1.9.0