diff --git a/cmd/gorelease/gorelease.go b/cmd/gorelease/gorelease.go index fffb42dfb..6347059bd 100644 --- a/cmd/gorelease/gorelease.go +++ b/cmd/gorelease/gorelease.go @@ -80,6 +80,7 @@ package main import ( + "bytes" "context" "encoding/json" "errors" @@ -95,6 +96,7 @@ import ( "path/filepath" "sort" "strings" + "unicode" "golang.org/x/exp/apidiff" "golang.org/x/mod/modfile" @@ -426,6 +428,12 @@ func loadLocalModule(ctx context.Context, modRoot, repoRoot, version string) (m m.highestTransitiveVersion = highestVersion } + retracted, err := loadRetractions(ctx, tmpLoadDir) + if err != nil { + return moduleInfo{}, err + } + m.diagnostics = append(m.diagnostics, retracted...) + return m, nil } @@ -1375,3 +1383,76 @@ func copyEnv(ctx context.Context, current []string) []string { copy(clone, env) return clone } + +// loadRetractions lists all retracted deps found at the modRoot. +func loadRetractions(ctx context.Context, modRoot string) ([]string, error) { + cmd := exec.CommandContext(ctx, "go", "list", "-json", "-m", "-u", "all") + if env, ok := ctx.Value("env").([]string); ok { + cmd.Env = env + } + cmd.Dir = modRoot + out, err := cmd.Output() + if err != nil { + return nil, cleanCmdError(err) + } + + var retracted []string + type message struct { + Path string + Version string + Retracted []string + } + + dec := json.NewDecoder(bytes.NewBuffer(out)) + for { + var m message + if err := dec.Decode(&m); err == io.EOF { + break + } else if err != nil { + return nil, err + } + if len(m.Retracted) == 0 { + continue + } + rationale, ok := shortRetractionRationale(m.Retracted) + if ok { + retracted = append(retracted, fmt.Sprintf("required module %s@%s retracted by module author: %s", m.Path, m.Version, rationale)) + } else { + retracted = append(retracted, fmt.Sprintf("required module %s@%s retracted by module author", m.Path, m.Version)) + } + } + + return retracted, nil +} + +// ShortRetractionRationale returns a retraction rationale string that is safe +// to print in a terminal. It returns hard-coded strings if the rationale +// is empty, too long, or contains non-printable characters. +// +// It returns true if the rationale was printable, and false if it was not (too +// long, contains graphics, etc). +func shortRetractionRationale(rationales []string) (string, bool) { + if len(rationales) == 0 { + return "", false + } + rationale := rationales[0] + + const maxRationaleBytes = 500 + if i := strings.Index(rationale, "\n"); i >= 0 { + rationale = rationale[:i] + } + rationale = strings.TrimSpace(rationale) + if rationale == "" || rationale == "retracted by module author" { + return "", false + } + if len(rationale) > maxRationaleBytes { + return "", false + } + for _, r := range rationale { + if !unicode.IsGraphic(r) && !unicode.IsSpace(r) { + return "", false + } + } + // NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here. + return rationale, true +} diff --git a/cmd/gorelease/testdata/mod/example.com_retract_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retract_v0.0.1.txt new file mode 100644 index 000000000..b1466d0b4 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retract_v0.0.1.txt @@ -0,0 +1,13 @@ +-- go.mod -- +module example.com/retract + +go 1.12 + +require example.com/retractdep v1.0.0 +-- go.sum -- +example.com/retractdep v1.0.0 h1:SOVn6jA2ygQY+v8/5aAwxVUJ9teuLrdH/UmbUtp2C44= +example.com/retractdep v1.0.0/go.mod h1:UjjWSH/ulfbAGgQQwm7pAZ988MFRngUSkJnzcuPsYDI= +-- a.go -- +package a + +import _ "example.com/retractdep" diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.0.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.0.txt new file mode 100644 index 000000000..36aa3d906 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.0.txt @@ -0,0 +1,8 @@ +-- go.mod -- +module example.com/retractdep + +go 1.12 +-- a.go -- +package a + +const A = "a" \ No newline at end of file diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.1.txt new file mode 100644 index 000000000..7548ed296 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v1.0.1.txt @@ -0,0 +1,11 @@ +-- go.mod -- +module example.com/retractdep + +go 1.12 + +// Remote-triggered crash in package foo. See CVE-2021-01234. +retract v1.0.0 +-- a.go -- +package a + +const A = "a" \ No newline at end of file diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.0.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.0.txt new file mode 100644 index 000000000..77619e32c --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.0.txt @@ -0,0 +1,12 @@ +# Identical to v1.0.0: just need a new version so that we can test different +# error messages based on the vX.0.1 retraction comments. We can't test them in +# the same major version because go mod will always use the latest version's +# error message. +-- go.mod -- +module example.com/retractdep/v2 + +go 1.12 +-- a.go -- +package a + +const A = "a" \ No newline at end of file diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.1.txt new file mode 100644 index 000000000..5682baaf8 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v2_v2.0.1.txt @@ -0,0 +1,10 @@ +-- go.mod -- +module example.com/retractdep/v2 + +go 1.12 + +retract v2.0.0 +-- a.go -- +package a + +const A = "a" \ No newline at end of file diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.0.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.0.txt new file mode 100644 index 000000000..1d526a96c --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.0.txt @@ -0,0 +1,12 @@ +# Identical to v1.0.0: just need a new version so that we can test different +# error messages based on the vX.0.1 retraction comments. We can't test them in +# the same major version because go mod will always use the latest version's +# error message. +-- go.mod -- +module example.com/retractdep/v3 + +go 1.12 +-- a.go -- +package a + +const A = "a" \ No newline at end of file diff --git a/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.1.txt new file mode 100644 index 000000000..ed39efba3 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retractdep_v3_v3.0.1.txt @@ -0,0 +1,11 @@ +-- go.mod -- +module example.com/retractdep/v3 + +go 1.12 + +// This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. This is a very long message. +retract v3.0.0 +-- a.go -- +package a + +const A = "a" \ No newline at end of file diff --git a/cmd/gorelease/testdata/mod/example.com_retracttransitive_v0.0.1.txt b/cmd/gorelease/testdata/mod/example.com_retracttransitive_v0.0.1.txt new file mode 100644 index 000000000..885906ff6 --- /dev/null +++ b/cmd/gorelease/testdata/mod/example.com_retracttransitive_v0.0.1.txt @@ -0,0 +1,15 @@ +-- go.mod -- +module example.com/retracttransitive + +go 1.12 + +require example.com/retract v0.0.1 +-- go.sum -- +example.com/retract v0.0.1 h1:Afj8efoHilltHZNLlEARzpc1Vkc5d6ugWKIE/YDmXuQ= +example.com/retract v0.0.1/go.mod h1:DUqXjcGF3aJhkjxsUjQ0DG65b51DDBvFrEbcr9kkyto= +example.com/retractdep v1.0.0 h1:SOVn6jA2ygQY+v8/5aAwxVUJ9teuLrdH/UmbUtp2C44= +example.com/retractdep v1.0.0/go.mod h1:UjjWSH/ulfbAGgQQwm7pAZ988MFRngUSkJnzcuPsYDI= +-- a.go -- +package a + +import _ "example.com/retract" diff --git a/cmd/gorelease/testdata/retract/retract_verify_direct_dep.test b/cmd/gorelease/testdata/retract/retract_verify_direct_dep.test new file mode 100644 index 000000000..7e923c5b2 --- /dev/null +++ b/cmd/gorelease/testdata/retract/retract_verify_direct_dep.test @@ -0,0 +1,6 @@ +mod=example.com/retract +version=v0.0.1 +success=false +-- want -- +Inferred base version: v0.0.1 +required module example.com/retractdep@v1.0.0 retracted by module author: Remote-triggered crash in package foo. See CVE-2021-01234. diff --git a/cmd/gorelease/testdata/retract/retract_verify_long_msg.test b/cmd/gorelease/testdata/retract/retract_verify_long_msg.test new file mode 100644 index 000000000..a5d9fb7a3 --- /dev/null +++ b/cmd/gorelease/testdata/retract/retract_verify_long_msg.test @@ -0,0 +1,18 @@ +mod=example.com/retract +success=false +-- want -- +Inferred base version: v0.0.1 +required module example.com/retractdep/v3@v3.0.0 retracted by module author +-- go.mod -- +module example.com/retract + +go 1.12 + +require example.com/retractdep/v3 v3.0.0 +-- go.sum -- +example.com/retractdep/v3 v3.0.0 h1:LEaqsEpt7J4Er+qSPqL7bENpIkRdZdaOE6KaUaiNB5I= +example.com/retractdep/v3 v3.0.0/go.mod h1:B2rEwAWayv3FJ2jyeiq9O3UBbxSvdDqZUtxmKsLyg6k= +-- a.go -- +package a + +import _ "example.com/retractdep/v3" diff --git a/cmd/gorelease/testdata/retract/retract_verify_no_msg.test b/cmd/gorelease/testdata/retract/retract_verify_no_msg.test new file mode 100644 index 000000000..9a4276062 --- /dev/null +++ b/cmd/gorelease/testdata/retract/retract_verify_no_msg.test @@ -0,0 +1,18 @@ +mod=example.com/retract +success=false +-- want -- +Inferred base version: v0.0.1 +required module example.com/retractdep/v2@v2.0.0 retracted by module author +-- go.mod -- +module example.com/retract + +go 1.12 + +require example.com/retractdep/v2 v2.0.0 +-- go.sum -- +example.com/retractdep/v2 v2.0.0 h1:ehV4yfX3A3jNlRnBmHPxq1TyVs1EhmCYI5miEva6Gv8= +example.com/retractdep/v2 v2.0.0/go.mod h1:rV+p/Yqwnupg15GPVGFRq+un/MYczBZcF1IZ8ubecag= +-- a.go -- +package a + +import _ "example.com/retractdep/v2" diff --git a/cmd/gorelease/testdata/retract/retract_verify_transitive_dep.test b/cmd/gorelease/testdata/retract/retract_verify_transitive_dep.test new file mode 100644 index 000000000..8338e5f2d --- /dev/null +++ b/cmd/gorelease/testdata/retract/retract_verify_transitive_dep.test @@ -0,0 +1,8 @@ +# When a retracted version is transitively depended upon, it should still +# result in a retraction error. +mod=example.com/retracttransitive +version=v0.0.1 +success=false +-- want -- +Inferred base version: v0.0.1 +required module example.com/retractdep@v1.0.0 retracted by module author: Remote-triggered crash in package foo. See CVE-2021-01234.