go 1.22.12: Fix CVE-2025-68119

Upstream Repository: https://github.com/golang/go.git

Bug details: https://nvd.nist.gov/vuln/detail/CVE-2025-68119
Type: Security Fix
CVE: CVE-2025-68119
Score: 7.0
Patch:
[1] https://github.com/golang/go/commit/62452bed4801
[2] https://github.com/golang/go/commit/73fe85f0ea1b

Note:
- First commit [1] is a dependent patch which is required additionally
  in original fix [2] to define ENV variable changes in
  src/cmd/go/internal/vcs/vcs.go file.

(From OE-Core rev: ef995146623cf65c2e30f37b09847883ca7481bb)

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
Signed-off-by: Paul Barker <paul@pbarker.dev>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Deepak Rathore 2026-02-11 21:01:50 -08:00 committed by Richard Purdie
parent a231c49abc
commit c13443407a
3 changed files with 1005 additions and 0 deletions

View File

@ -35,6 +35,8 @@ SRC_URI += "\
file://CVE-2025-61726.patch \ file://CVE-2025-61726.patch \
file://CVE-2025-61728.patch \ file://CVE-2025-61728.patch \
file://CVE-2025-61731.patch \ file://CVE-2025-61731.patch \
file://CVE-2025-68119-dependent.patch \
file://CVE-2025-68119.patch \
" "
SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71" SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71"

View File

@ -0,0 +1,175 @@
From 121b6cb231b5d904c03739495fcda69152d83f88 Mon Sep 17 00:00:00 2001
From: Matt Harbison <mharbison72@gmail.com>
Date: Sat, 3 Aug 2024 00:06:30 +0000
Subject: [PATCH] cmd/go: fix the accuracy of Mercurial vcs.* stamped data
There were a few Mercurial command line uses that could cause the wrong
data to be used:
* The log command needs '-r.' to specify the currently checked out commit
* HGPLAIN is needed to disable optional output on commands
* '-S' is needed to for the 'status' command to recurse into any subrepos
The most likely issue to be seen here was the use of '-l1' instead of
'-r.', which prints the most recent commit instead of the current checkout.
Since tagging in Mercurial creates a new commit, this basically means the
data was wrong for every tagged build.
This also adds an hgrc config file to the test, with config options to
keep the time and author values fixed. It's what's used in the Mercurial
test harness to keep the commit hashes stable, and allows the tests here to
also match the time and the revision ID, to prevent regressing.
Fixes #63532
CVE: CVE-2025-68119
Upstream-Status: Backport [https://github.com/golang/go/commit/62452bed4801]
Change-Id: I5b9971ce87c83431ec77e4a002bdc33fcf393856
GitHub-Last-Rev: 62c9db0a28fee5881d0fe49f7bbb6e1653c7ff60
GitHub-Pull-Request: golang/go#63557
Reviewed-on: https://go-review.googlesource.com/c/go/+/535377
Reviewed-by: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Sam Thanawalla <samthanawalla@google.com>
Auto-Submit: Sam Thanawalla <samthanawalla@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
(cherry picked from commit 62452bed480108623910feace4a5cea5448e6822)
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
src/cmd/go/internal/vcs/vcs.go | 13 +++++--
.../testdata/script/version_buildvcs_hg.txt | 39 ++++++++++++++++---
2 files changed, 43 insertions(+), 9 deletions(-)
diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go
index 89d9f0e94e..60f76d77cf 100644
--- a/src/cmd/go/internal/vcs/vcs.go
+++ b/src/cmd/go/internal/vcs/vcs.go
@@ -37,6 +37,7 @@ import (
type Cmd struct {
Name string
Cmd string // name of binary to invoke command
+ Env []string // any environment values to set/override
RootNames []rootName // filename and mode indicating the root of a checkout directory
CreateCmd []string // commands to download a fresh copy of a repository
@@ -154,6 +155,10 @@ func vcsByCmd(cmd string) *Cmd {
var vcsHg = &Cmd{
Name: "Mercurial",
Cmd: "hg",
+
+ // HGPLAIN=1 turns off additional output that a user may have enabled via
+ // config options or certain extensions.
+ Env: []string{"HGPLAIN=1"},
RootNames: []rootName{
{filename: ".hg", isDir: true},
},
@@ -189,12 +194,11 @@ func hgRemoteRepo(vcsHg *Cmd, rootDir string) (remoteRepo string, err error) {
func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
// Output changeset ID and seconds since epoch.
- out, err := vcsHg.runOutputVerboseOnly(rootDir, `log -l1 -T {node}:{date|hgdate}`)
+ out, err := vcsHg.runOutputVerboseOnly(rootDir, `log -r. -T {node}:{date|hgdate}`)
if err != nil {
return Status{}, err
}
- // Successful execution without output indicates an empty repo (no commits).
var rev string
var commitTime time.Time
if len(out) > 0 {
@@ -209,7 +213,7 @@ func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
}
// Also look for untracked files.
- out, err = vcsHg.runOutputVerboseOnly(rootDir, "status")
+ out, err = vcsHg.runOutputVerboseOnly(rootDir, "status -S")
if err != nil {
return Status{}, err
}
@@ -689,6 +693,9 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
cmd := exec.Command(v.Cmd, args...)
cmd.Dir = dir
+ if v.Env != nil {
+ cmd.Env = append(cmd.Environ(), v.Env...)
+ }
if cfg.BuildX {
fmt.Fprintf(os.Stderr, "cd %s\n", dir)
fmt.Fprintf(os.Stderr, "%s %s\n", v.Cmd, strings.Join(args, " "))
diff --git a/src/cmd/go/testdata/script/version_buildvcs_hg.txt b/src/cmd/go/testdata/script/version_buildvcs_hg.txt
index fbbd886102..13904fae12 100644
--- a/src/cmd/go/testdata/script/version_buildvcs_hg.txt
+++ b/src/cmd/go/testdata/script/version_buildvcs_hg.txt
@@ -6,6 +6,8 @@
[short] skip
env GOBIN=$WORK/gopath/bin
env oldpath=$PATH
+env TZ=GMT
+env HGRCPATH=$WORK/hgrc
cd repo/a
# If there's no local repository, there's no VCS info.
@@ -29,24 +31,43 @@ cd ..
env PATH=$oldpath
rm .hg
-# If there is an empty repository in a parent directory, only "uncommitted" is tagged.
+# An empty repository or one explicitly updated to null uses the null cset ID,
+# and the time is hard set to 1/1/70, regardless of the current time.
exec hg init
cd a
go install
go version -m $GOBIN/a$GOEXE
-! stdout vcs.revision
-! stdout vcs.time
+stdout '^\tbuild\tvcs.revision=0000000000000000000000000000000000000000$'
+stdout '^\tbuild\tvcs.time=1970-01-01T00:00:00Z$'
stdout '^\tbuild\tvcs.modified=true$'
cd ..
# Revision and commit time are tagged for repositories with commits.
exec hg add a README
-exec hg commit -m 'initial commit'
+exec hg commit -m 'initial commit' --user test-user --date '2024-07-31T01:21:27+00:00'
cd a
go install
go version -m $GOBIN/a$GOEXE
-stdout '^\tbuild\tvcs.revision='
-stdout '^\tbuild\tvcs.time='
+stdout '^\tbuild\tvcs.revision=71eaed52daeaafea83cb604f75b0a0336ef2c345$'
+stdout '^\tbuild\tvcs.time=2024-07-31T01:21:27Z$'
+stdout '^\tbuild\tvcs.modified=false$'
+rm $GOBIN/a$GOEXE
+
+# Add an extra commit and then back off of it to show that the hash is
+# from the checked out revision, not the tip revision.
+cp ../../outside/empty.txt .
+exec hg ci -Am 'another commit' --user test-user --date '2024-08-01T19:24:38+00:00'
+exec hg update --clean -r '.^'
+
+# Modified state is not thrown off by extra status output
+exec hg bisect -v -g .
+exec hg bisect -v -b '.^^'
+exec hg status
+stdout '^.+'
+go install
+go version -m $GOBIN/a$GOEXE
+stdout '^\tbuild\tvcs.revision=71eaed52daeaafea83cb604f75b0a0336ef2c345$'
+stdout '^\tbuild\tvcs.time=2024-07-31T01:21:27Z$'
stdout '^\tbuild\tvcs.modified=false$'
rm $GOBIN/a$GOEXE
@@ -88,4 +109,10 @@ go 1.18
package main
func main() {}
+-- $WORK/hgrc --
+[ui]
+# tweakdefaults is an opt-in that may print extra output in commands like
+# status. That can be disabled by setting HGPLAIN=1.
+tweakdefaults = 1
+
-- outside/empty.txt --
--
2.35.6

View File

@ -0,0 +1,828 @@
From 204e2fdacfbdb72a0b85fb526c8599128e430e94 Mon Sep 17 00:00:00 2001
From: Roland Shoemaker <bracewell@google.com>
Date: Wed, 10 Dec 2025 08:13:07 -0500
Subject: [PATCH] [release-branch.go1.24] cmd/go: update VCS commands to use
safer flag/argument syntax
In various situations, the toolchain invokes VCS commands. Some of these
commands take arbitrary input, either provided by users or fetched from
external sources. To prevent potential command injection vulnerabilities
or misinterpretation of arguments as flags, this change updates the VCS
commands to use various techniques to separate flags from positional
arguments, and to directly associate flags with their values.
Additionally, we update the environment variable for Mercurial to use
`HGPLAIN=+strictflags`, which is the more explicit way to disable user
configurations (intended or otherwise) that might interfere with command
execution.
We also now disallow version strings from being prefixed with '-' or
'/', as doing so opens us up to making the same mistake again in the
future. As far as we know there are currently ~0 public modules affected
by this.
While I was working on cmd/go/internal/vcs, I also noticed that a
significant portion of the commands being implemented were dead code.
In order to reduce the maintenance burden and surface area for potential
issues, I removed the dead code for unused commands.
We should probably follow up with a more structured change to make it
harder to accidentally re-introduce these issues in the future, but for
now this addresses the issue at hand.
Thanks to splitline (@splitline) from DEVCORE Research Team for
reporting this issue.
Fixes CVE-2025-68119
Updates #77099
Fixes #77103
CVE: CVE-2025-68119
Upstream-Status: Backport [https://github.com/golang/go/commit/73fe85f0ea1b]
Backport Changes:
- In file src/cmd/go/internal/modfetch/codehost/git.go, Replaced the
function call runGIT with RUN. This changes is not present in current
version v1.22.12 and this changes were introduced in version v1.24 by
this commit https://github.com/golang/go/commit/8aa2eed8fb90
Change-Id: I9d9f4ee05b95be49fe14edf71a1b8e6c0784378e
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3260
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/736710
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit 94a1296a457387d1fd6eca1a9bcd44e89bdd9d55)
Reviewed-on: https://go-review.googlesource.com/c/go/+/739421
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
(cherry picked from commit 73fe85f0ea1bf2cec8e9a89bf5645de06ecaa0a6)
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
src/cmd/go/internal/modcmd/edit.go | 10 +-
src/cmd/go/internal/modfetch/codehost/git.go | 20 +-
src/cmd/go/internal/modfetch/codehost/vcs.go | 20 +-
src/cmd/go/internal/modget/query.go | 5 +-
src/cmd/go/internal/modload/build.go | 12 +-
src/cmd/go/internal/modload/list.go | 30 +-
src/cmd/go/internal/toolchain/select.go | 7 +-
src/cmd/go/internal/vcs/vcs.go | 331 +------------------
src/cmd/go/internal/workcmd/edit.go | 5 +-
9 files changed, 96 insertions(+), 344 deletions(-)
diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go
index db131b0881..330603fe32 100644
--- a/src/cmd/go/internal/modcmd/edit.go
+++ b/src/cmd/go/internal/modcmd/edit.go
@@ -284,7 +284,10 @@ func runEdit(ctx context.Context, cmd *base.Command, args []string) {
// parsePathVersion parses -flag=arg expecting arg to be path@version.
func parsePathVersion(flag, arg string) (path, version string) {
- before, after, found := strings.Cut(arg, "@")
+ before, after, found, err := modload.ParsePathVersion(arg)
+ if err != nil {
+ base.Fatalf("go: -%s=%s: %v", flag, arg, err)
+ }
if !found {
base.Fatalf("go: -%s=%s: need path@version", flag, arg)
}
@@ -318,7 +321,10 @@ func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version
if allowDirPath && modfile.IsDirectoryPath(arg) {
return arg, "", nil
}
- before, after, found := strings.Cut(arg, "@")
+ before, after, found, err := modload.ParsePathVersion(arg)
+ if err != nil {
+ return "", "", err
+ }
if !found {
path = arg
} else {
diff --git a/src/cmd/go/internal/modfetch/codehost/git.go b/src/cmd/go/internal/modfetch/codehost/git.go
index 9996be7af7..45727ae3fb 100644
--- a/src/cmd/go/internal/modfetch/codehost/git.go
+++ b/src/cmd/go/internal/modfetch/codehost/git.go
@@ -246,7 +246,7 @@ func (r *gitRepo) loadRefs(ctx context.Context) (map[string]string, error) {
r.refsErr = err
return
}
- out, gitErr := Run(ctx, r.dir, "git", "ls-remote", "-q", r.remote)
+ out, gitErr := Run(ctx, r.dir, "git", "ls-remote", "-q","--end-of-options", r.remote)
release()
if gitErr != nil {
@@ -509,7 +509,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
if fromTag && !slices.Contains(info.Tags, tag) {
// The local repo includes the commit hash we want, but it is missing
// the corresponding tag. Add that tag and try again.
- _, err := Run(ctx, r.dir, "git", "tag", tag, hash)
+ _, err := Run(ctx, r.dir, "git", "tag","--end-of-options", tag, hash)
if err != nil {
return nil, err
}
@@ -554,7 +554,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
// an apparent Git bug introduced in Git 2.21 (commit 61c771),
// which causes the handler for protocol version 1 to sometimes miss
// tags that point to the requested commit (see https://go.dev/issue/56881).
- _, err = Run(ctx, r.dir, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1", r.remote, refspec)
+ _, err = Run(ctx, r.dir, "git", "-c", "protocol.version=2", "fetch", "-f", "--depth=1","--end-of-options", r.remote, refspec)
release()
if err == nil {
@@ -597,12 +597,12 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
}
defer release()
- if _, err := Run(ctx, r.dir, "git", "fetch", "-f", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
+ if _, err := Run(ctx, r.dir, "git", "fetch", "-f","--end-of-options", r.remote, "refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"); err != nil {
return err
}
if _, err := os.Stat(filepath.Join(r.dir, "shallow")); err == nil {
- if _, err := Run(ctx, r.dir, "git", "fetch", "--unshallow", "-f", r.remote); err != nil {
+ if _, err := Run(ctx, r.dir, "git", "fetch", "--unshallow", "-f", "--end-of-options",r.remote); err != nil {
return err
}
}
@@ -615,7 +615,7 @@ func (r *gitRepo) fetchRefsLocked(ctx context.Context) error {
// statLocal returns a new RevInfo describing rev in the local git repository.
// It uses version as info.Version.
func (r *gitRepo) statLocal(ctx context.Context, version, rev string) (*RevInfo, error) {
- out, err := Run(ctx, r.dir, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", rev, "--")
+ out, err := Run(ctx, r.dir, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D","--end-of-options", rev, "--")
if err != nil {
// Return info with Origin.RepoSum if possible to allow caching of negative lookup.
var info *RevInfo
@@ -705,7 +705,7 @@ func (r *gitRepo) ReadFile(ctx context.Context, rev, file string, maxSize int64)
if err != nil {
return nil, err
}
- out, err := Run(ctx, r.dir, "git", "cat-file", "blob", info.Name+":"+file)
+ out, err := Run(ctx, r.dir, "git", "cat-file","--end-of-options", "blob", info.Name+":"+file)
if err != nil {
return nil, fs.ErrNotExist
}
@@ -723,7 +723,7 @@ func (r *gitRepo) RecentTag(ctx context.Context, rev, prefix string, allowed fun
// result is definitive.
describe := func() (definitive bool) {
var out []byte
- out, err = Run(ctx, r.dir, "git", "for-each-ref", "--format", "%(refname)", "refs/tags", "--merged", rev)
+ out, err = Run(ctx, r.dir, "git", "for-each-ref", "--format=%(refname)", "--merged="+rev)
if err != nil {
return true
}
@@ -865,7 +865,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
// TODO: Use maxSize or drop it.
args := []string{}
if subdir != "" {
- args = append(args, "--", subdir)
+ args = append(args, subdir)
}
info, err := r.Stat(ctx, rev) // download rev into local git repo
if err != nil {
@@ -887,7 +887,7 @@ func (r *gitRepo) ReadZip(ctx context.Context, rev, subdir string, maxSize int64
// text file line endings. Setting -c core.autocrlf=input means only
// translate files on the way into the repo, not on the way out (archive).
// The -c core.eol=lf should be unnecessary but set it anyway.
- archive, err := Run(ctx, r.dir, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", info.Name, args)
+ archive, err := Run(ctx, r.dir, "git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", "--end-of-options", info.Name, args)
if err != nil {
if bytes.Contains(err.(*RunError).Stderr, []byte("did not match any files")) {
return nil, fs.ErrNotExist
diff --git a/src/cmd/go/internal/modfetch/codehost/vcs.go b/src/cmd/go/internal/modfetch/codehost/vcs.go
index 5bd100556b..425f61269f 100644
--- a/src/cmd/go/internal/modfetch/codehost/vcs.go
+++ b/src/cmd/go/internal/modfetch/codehost/vcs.go
@@ -162,20 +162,20 @@ var vcsCmds = map[string]*vcsCmd{
branchRE: re(`(?m)^[^\n]+$`),
badLocalRevRE: re(`(?m)^(tip)$`),
statLocal: func(rev, remote string) []string {
- return []string{"hg", "log", "-l1", "-r", rev, "--template", "{node} {date|hgdate} {tags}"}
+ return []string{"hg", "log", "-l1", fmt.Sprintf("--rev=%s", rev), "--template", "{node} {date|hgdate} {tags}"}
},
parseStat: hgParseStat,
fetch: []string{"hg", "pull", "-f"},
latest: "tip",
readFile: func(rev, file, remote string) []string {
- return []string{"hg", "cat", "-r", rev, file}
+ return []string{"hg", "cat", fmt.Sprintf("--rev=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
pattern := []string{}
if subdir != "" {
- pattern = []string{"-I", subdir + "/**"}
+ pattern = []string{fmt.Sprintf("--include=%s", subdir+"/**")}
}
- return str.StringList("hg", "archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/", pattern, "--", target)
+ return str.StringList("hg", "archive", "-t", "zip", "--no-decode", fmt.Sprintf("--rev=%s", rev), "--prefix=prefix/", pattern, "--", target)
},
},
@@ -215,19 +215,19 @@ var vcsCmds = map[string]*vcsCmd{
tagRE: re(`(?m)^\S+`),
badLocalRevRE: re(`^revno:-`),
statLocal: func(rev, remote string) []string {
- return []string{"bzr", "log", "-l1", "--long", "--show-ids", "-r", rev}
+ return []string{"bzr", "log", "-l1", "--long", "--show-ids", fmt.Sprintf("--revision=%s", rev)}
},
parseStat: bzrParseStat,
latest: "revno:-1",
readFile: func(rev, file, remote string) []string {
- return []string{"bzr", "cat", "-r", rev, file}
+ return []string{"bzr", "cat", fmt.Sprintf("--revision=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
extra := []string{}
if subdir != "" {
extra = []string{"./" + subdir}
}
- return str.StringList("bzr", "export", "--format=zip", "-r", rev, "--root=prefix/", "--", target, extra)
+ return str.StringList("bzr", "export", "--format=zip", fmt.Sprintf("--revision=%s", rev), "--root=prefix/", "--", target, extra)
},
},
@@ -242,17 +242,17 @@ var vcsCmds = map[string]*vcsCmd{
},
tagRE: re(`XXXTODO`),
statLocal: func(rev, remote string) []string {
- return []string{"fossil", "info", "-R", ".fossil", rev}
+ return []string{"fossil", "info", "-R", ".fossil", "--", rev}
},
parseStat: fossilParseStat,
latest: "trunk",
readFile: func(rev, file, remote string) []string {
- return []string{"fossil", "cat", "-R", ".fossil", "-r", rev, file}
+ return []string{"fossil", "cat", "-R", ".fossil", fmt.Sprintf("-r=%s", rev), "--", file}
},
readZip: func(rev, subdir, remote, target string) []string {
extra := []string{}
if subdir != "" && !strings.ContainsAny(subdir, "*?[],") {
- extra = []string{"--include", subdir}
+ extra = []string{fmt.Sprintf("--include=%s", subdir)}
}
// Note that vcsRepo.ReadZip below rewrites this command
// to run in a different directory, to work around a fossil bug.
diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go
index 498ba6c2ff..0d33a52677 100644
--- a/src/cmd/go/internal/modget/query.go
+++ b/src/cmd/go/internal/modget/query.go
@@ -139,7 +139,10 @@ func errSet(err error) pathSet { return pathSet{err: err} }
// newQuery returns a new query parsed from the raw argument,
// which must be either path or path@version.
func newQuery(raw string) (*query, error) {
- pattern, rawVers, found := strings.Cut(raw, "@")
+ pattern, rawVers, found, err := modload.ParsePathVersion(raw)
+ if err != nil {
+ return nil, err
+ }
if found && (strings.Contains(rawVers, "@") || rawVers == "") {
return nil, fmt.Errorf("invalid module version syntax %q", raw)
}
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 5cf1487c3e..08acf3aa2b 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -12,7 +12,6 @@ import (
"io/fs"
"os"
"path/filepath"
- "strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
@@ -88,7 +87,16 @@ func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
return nil
}
- if path, vers, found := strings.Cut(path, "@"); found {
+ path, vers, found, err := ParsePathVersion(path)
+ if err != nil {
+ return &modinfo.ModulePublic{
+ Path: path,
+ Error: &modinfo.ModuleError{
+ Err: err.Error(),
+ },
+ }
+ }
+ if found {
m := module.Version{Path: path, Version: vers}
return moduleInfo(ctx, nil, m, 0, nil)
}
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index ef93c25121..e9efb1918e 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -149,7 +149,11 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
}
continue
}
- if path, vers, found := strings.Cut(arg, "@"); found {
+ path, vers, found, err := ParsePathVersion(arg)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ if found {
if vers == "upgrade" || vers == "patch" {
if _, ok := rs.rootSelected(path); !ok || rs.pruning == unpruned {
needFullGraph = true
@@ -175,7 +179,11 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
matchedModule := map[module.Version]bool{}
for _, arg := range args {
- if path, vers, found := strings.Cut(arg, "@"); found {
+ path, vers, found, err := ParsePathVersion(arg)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ if found {
var current string
if mg == nil {
current, _ = rs.rootSelected(path)
@@ -308,3 +316,21 @@ func modinfoError(path, vers string, err error) *modinfo.ModuleError {
return &modinfo.ModuleError{Err: err.Error()}
}
+
+// ParsePathVersion parses arg expecting arg to be path@version. If there is no
+// '@' in arg, found is false, vers is "", and path is arg. This mirrors the
+// typical usage of strings.Cut. ParsePathVersion is meant to be a general
+// replacement for strings.Cut in module version parsing. If the version is
+// invalid, an error is returned. The version is considered invalid if it is
+// prefixed with '-' or '/', which can cause security problems when constructing
+// commands to execute that use the version.
+func ParsePathVersion(arg string) (path, vers string, found bool, err error) {
+ path, vers, found = strings.Cut(arg, "@")
+ if !found {
+ return arg, "", false, nil
+ }
+ if len(vers) > 0 && (vers[0] == '-' || vers[0] == '/') {
+ return "", "", false, fmt.Errorf("invalid module version %q", vers)
+ }
+ return path, vers, true, nil
+}
diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go
index 14a8d3c21d..838ebae6a7 100644
--- a/src/cmd/go/internal/toolchain/select.go
+++ b/src/cmd/go/internal/toolchain/select.go
@@ -614,7 +614,10 @@ func goInstallVersion() bool {
if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) {
return false
}
- path, version, _ := strings.Cut(pkgArg, "@")
+ path, version, _, err := modload.ParsePathVersion(pkgArg)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
if path == "" || version == "" || gover.IsToolchain(path) {
return false
}
@@ -650,7 +653,7 @@ func goInstallVersion() bool {
allowed = nil
}
noneSelected := func(path string) (version string) { return "none" }
- _, err := modload.QueryPackages(ctx, path, version, noneSelected, allowed)
+ _, err = modload.QueryPackages(ctx, path, version, noneSelected, allowed)
if errors.Is(err, gover.ErrTooNew) {
// Run early switch, same one go install or go run would eventually do,
// if it understood all the command-line flags.
diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go
index 60f76d77cf..55bf25ff62 100644
--- a/src/cmd/go/internal/vcs/vcs.go
+++ b/src/cmd/go/internal/vcs/vcs.go
@@ -17,7 +17,6 @@ import (
"os"
"os/exec"
"path/filepath"
- "regexp"
"strconv"
"strings"
"sync"
@@ -40,20 +39,10 @@ type Cmd struct {
Env []string // any environment values to set/override
RootNames []rootName // filename and mode indicating the root of a checkout directory
- CreateCmd []string // commands to download a fresh copy of a repository
- DownloadCmd []string // commands to download updates into an existing repository
-
- TagCmd []tagCmd // commands to list tags
- TagLookupCmd []tagCmd // commands to lookup tags before running tagSyncCmd
- TagSyncCmd []string // commands to sync to specific tag
- TagSyncDefault []string // commands to sync to default tag
-
Scheme []string
PingCmd string
- RemoteRepo func(v *Cmd, rootDir string) (remoteRepo string, err error)
- ResolveRepo func(v *Cmd, rootDir, remoteRepo string) (realRepo string, err error)
- Status func(v *Cmd, rootDir string) (Status, error)
+ Status func(v *Cmd, rootDir string) (Status, error)
}
// Status is the current state of a local repository.
@@ -156,40 +145,16 @@ var vcsHg = &Cmd{
Name: "Mercurial",
Cmd: "hg",
- // HGPLAIN=1 turns off additional output that a user may have enabled via
- // config options or certain extensions.
- Env: []string{"HGPLAIN=1"},
+ // HGPLAIN=+strictflags turns off additional output that a user may have
+ // enabled via config options or certain extensions.
+ Env: []string{"HGPLAIN=+strictflags"},
RootNames: []rootName{
{filename: ".hg", isDir: true},
},
- CreateCmd: []string{"clone -U -- {repo} {dir}"},
- DownloadCmd: []string{"pull"},
-
- // We allow both tag and branch names as 'tags'
- // for selecting a version. This lets people have
- // a go.release.r60 branch and a go1 branch
- // and make changes in both, without constantly
- // editing .hgtags.
- TagCmd: []tagCmd{
- {"tags", `^(\S+)`},
- {"branches", `^(\S+)`},
- },
- TagSyncCmd: []string{"update -r {tag}"},
- TagSyncDefault: []string{"update default"},
-
- Scheme: []string{"https", "http", "ssh"},
- PingCmd: "identify -- {scheme}://{repo}",
- RemoteRepo: hgRemoteRepo,
- Status: hgStatus,
-}
-
-func hgRemoteRepo(vcsHg *Cmd, rootDir string) (remoteRepo string, err error) {
- out, err := vcsHg.runOutput(rootDir, "paths default")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(out)), nil
+ Scheme: []string{"https", "http", "ssh"},
+ PingCmd: "identify -- {scheme}://{repo}",
+ Status: hgStatus,
}
func hgStatus(vcsHg *Cmd, rootDir string) (Status, error) {
@@ -252,25 +217,6 @@ var vcsGit = &Cmd{
{filename: ".git", isDir: true},
},
- CreateCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
- DownloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
-
- TagCmd: []tagCmd{
- // tags/xxx matches a git tag named xxx
- // origin/xxx matches a git branch named xxx on the default remote repository
- {"show-ref", `(?:tags|origin)/(\S+)$`},
- },
- TagLookupCmd: []tagCmd{
- {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
- },
- TagSyncCmd: []string{"checkout {tag}", "submodule update --init --recursive"},
- // both createCmd and downloadCmd update the working dir.
- // No need to do more here. We used to 'checkout master'
- // but that doesn't work if the default branch is not named master.
- // DO NOT add 'checkout master' here.
- // See golang.org/issue/9032.
- TagSyncDefault: []string{"submodule update --init --recursive"},
-
Scheme: []string{"git", "https", "http", "git+ssh", "ssh"},
// Leave out the '--' separator in the ls-remote command: git 2.7.4 does not
@@ -279,54 +225,7 @@ var vcsGit = &Cmd{
// See golang.org/issue/33836.
PingCmd: "ls-remote {scheme}://{repo}",
- RemoteRepo: gitRemoteRepo,
- Status: gitStatus,
-}
-
-// scpSyntaxRe matches the SCP-like addresses used by Git to access
-// repositories by SSH.
-var scpSyntaxRe = lazyregexp.New(`^(\w+)@([\w.-]+):(.*)$`)
-
-func gitRemoteRepo(vcsGit *Cmd, rootDir string) (remoteRepo string, err error) {
- const cmd = "config remote.origin.url"
- outb, err := vcsGit.run1(rootDir, cmd, nil, false)
- if err != nil {
- // if it doesn't output any message, it means the config argument is correct,
- // but the config value itself doesn't exist
- if outb != nil && len(outb) == 0 {
- return "", errors.New("remote origin not found")
- }
- return "", err
- }
- out := strings.TrimSpace(string(outb))
-
- var repoURL *urlpkg.URL
- if m := scpSyntaxRe.FindStringSubmatch(out); m != nil {
- // Match SCP-like syntax and convert it to a URL.
- // Eg, "git@github.com:user/repo" becomes
- // "ssh://git@github.com/user/repo".
- repoURL = &urlpkg.URL{
- Scheme: "ssh",
- User: urlpkg.User(m[1]),
- Host: m[2],
- Path: m[3],
- }
- } else {
- repoURL, err = urlpkg.Parse(out)
- if err != nil {
- return "", err
- }
- }
-
- // Iterate over insecure schemes too, because this function simply
- // reports the state of the repo. If we can't see insecure schemes then
- // we can't report the actual repo URL.
- for _, s := range vcsGit.Scheme {
- if repoURL.Scheme == s {
- return repoURL.String(), nil
- }
- }
- return "", errors.New("unable to parse output of git " + cmd)
+ Status: gitStatus,
}
func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) {
@@ -366,62 +265,9 @@ var vcsBzr = &Cmd{
{filename: ".bzr", isDir: true},
},
- CreateCmd: []string{"branch -- {repo} {dir}"},
-
- // Without --overwrite bzr will not pull tags that changed.
- // Replace by --overwrite-tags after http://pad.lv/681792 goes in.
- DownloadCmd: []string{"pull --overwrite"},
-
- TagCmd: []tagCmd{{"tags", `^(\S+)`}},
- TagSyncCmd: []string{"update -r {tag}"},
- TagSyncDefault: []string{"update -r revno:-1"},
-
- Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
- PingCmd: "info -- {scheme}://{repo}",
- RemoteRepo: bzrRemoteRepo,
- ResolveRepo: bzrResolveRepo,
- Status: bzrStatus,
-}
-
-func bzrRemoteRepo(vcsBzr *Cmd, rootDir string) (remoteRepo string, err error) {
- outb, err := vcsBzr.runOutput(rootDir, "config parent_location")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(outb)), nil
-}
-
-func bzrResolveRepo(vcsBzr *Cmd, rootDir, remoteRepo string) (realRepo string, err error) {
- outb, err := vcsBzr.runOutput(rootDir, "info "+remoteRepo)
- if err != nil {
- return "", err
- }
- out := string(outb)
-
- // Expect:
- // ...
- // (branch root|repository branch): <URL>
- // ...
-
- found := false
- for _, prefix := range []string{"\n branch root: ", "\n repository branch: "} {
- i := strings.Index(out, prefix)
- if i >= 0 {
- out = out[i+len(prefix):]
- found = true
- break
- }
- }
- if !found {
- return "", fmt.Errorf("unable to parse output of bzr info")
- }
-
- i := strings.Index(out, "\n")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of bzr info")
- }
- out = out[:i]
- return strings.TrimSpace(out), nil
+ Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
+ PingCmd: "info -- {scheme}://{repo}",
+ Status: bzrStatus,
}
func bzrStatus(vcsBzr *Cmd, rootDir string) (Status, error) {
@@ -489,45 +335,11 @@ var vcsSvn = &Cmd{
{filename: ".svn", isDir: true},
},
- CreateCmd: []string{"checkout -- {repo} {dir}"},
- DownloadCmd: []string{"update"},
-
// There is no tag command in subversion.
// The branch information is all in the path names.
- Scheme: []string{"https", "http", "svn", "svn+ssh"},
- PingCmd: "info -- {scheme}://{repo}",
- RemoteRepo: svnRemoteRepo,
-}
-
-func svnRemoteRepo(vcsSvn *Cmd, rootDir string) (remoteRepo string, err error) {
- outb, err := vcsSvn.runOutput(rootDir, "info")
- if err != nil {
- return "", err
- }
- out := string(outb)
-
- // Expect:
- //
- // ...
- // URL: <URL>
- // ...
- //
- // Note that we're not using the Repository Root line,
- // because svn allows checking out subtrees.
- // The URL will be the URL of the subtree (what we used with 'svn co')
- // while the Repository Root may be a much higher parent.
- i := strings.Index(out, "\nURL: ")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of svn info")
- }
- out = out[i+len("\nURL: "):]
- i = strings.Index(out, "\n")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of svn info")
- }
- out = out[:i]
- return strings.TrimSpace(out), nil
+ Scheme: []string{"https", "http", "svn", "svn+ssh"},
+ PingCmd: "info -- {scheme}://{repo}",
}
// fossilRepoName is the name go get associates with a fossil repository. In the
@@ -543,24 +355,8 @@ var vcsFossil = &Cmd{
{filename: "_FOSSIL_", isDir: false},
},
- CreateCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
- DownloadCmd: []string{"up"},
-
- TagCmd: []tagCmd{{"tag ls", `(.*)`}},
- TagSyncCmd: []string{"up tag:{tag}"},
- TagSyncDefault: []string{"up trunk"},
-
- Scheme: []string{"https", "http"},
- RemoteRepo: fossilRemoteRepo,
- Status: fossilStatus,
-}
-
-func fossilRemoteRepo(vcsFossil *Cmd, rootDir string) (remoteRepo string, err error) {
- out, err := vcsFossil.runOutput(rootDir, "remote-url")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(out)), nil
+ Scheme: []string{"https", "http"},
+ Status: fossilStatus,
}
var errFossilInfo = errors.New("unable to parse output of fossil info")
@@ -661,7 +457,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
args[i] = expand(m, arg)
}
- if len(args) >= 2 && args[0] == "-go-internal-mkdir" {
+ if len(args) >= 2 && args[0] == "--go-internal-mkdir" {
var err error
if filepath.IsAbs(args[1]) {
err = os.Mkdir(args[1], fs.ModePerm)
@@ -674,7 +470,7 @@ func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([
args = args[2:]
}
- if len(args) >= 2 && args[0] == "-go-internal-cd" {
+ if len(args) >= 2 && args[0] == "--go-internal-cd" {
if filepath.IsAbs(args[1]) {
dir = args[1]
} else {
@@ -735,99 +531,6 @@ func (v *Cmd) Ping(scheme, repo string) error {
return v.runVerboseOnly(dir, v.PingCmd, "scheme", scheme, "repo", repo)
}
-// Create creates a new copy of repo in dir.
-// The parent of dir must exist; dir must not.
-func (v *Cmd) Create(dir, repo string) error {
- release, err := base.AcquireNet()
- if err != nil {
- return err
- }
- defer release()
-
- for _, cmd := range v.CreateCmd {
- if err := v.run(filepath.Dir(dir), cmd, "dir", dir, "repo", repo); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Download downloads any new changes for the repo in dir.
-func (v *Cmd) Download(dir string) error {
- release, err := base.AcquireNet()
- if err != nil {
- return err
- }
- defer release()
-
- for _, cmd := range v.DownloadCmd {
- if err := v.run(dir, cmd); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Tags returns the list of available tags for the repo in dir.
-func (v *Cmd) Tags(dir string) ([]string, error) {
- var tags []string
- for _, tc := range v.TagCmd {
- out, err := v.runOutput(dir, tc.cmd)
- if err != nil {
- return nil, err
- }
- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
- for _, m := range re.FindAllStringSubmatch(string(out), -1) {
- tags = append(tags, m[1])
- }
- }
- return tags, nil
-}
-
-// TagSync syncs the repo in dir to the named tag,
-// which either is a tag returned by tags or is v.tagDefault.
-func (v *Cmd) TagSync(dir, tag string) error {
- if v.TagSyncCmd == nil {
- return nil
- }
- if tag != "" {
- for _, tc := range v.TagLookupCmd {
- out, err := v.runOutput(dir, tc.cmd, "tag", tag)
- if err != nil {
- return err
- }
- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
- m := re.FindStringSubmatch(string(out))
- if len(m) > 1 {
- tag = m[1]
- break
- }
- }
- }
-
- release, err := base.AcquireNet()
- if err != nil {
- return err
- }
- defer release()
-
- if tag == "" && v.TagSyncDefault != nil {
- for _, cmd := range v.TagSyncDefault {
- if err := v.run(dir, cmd); err != nil {
- return err
- }
- }
- return nil
- }
-
- for _, cmd := range v.TagSyncCmd {
- if err := v.run(dir, cmd, "tag", tag); err != nil {
- return err
- }
- }
- return nil
-}
-
// A vcsPath describes how to convert an import path into a
// version control system and repository name.
type vcsPath struct {
diff --git a/src/cmd/go/internal/workcmd/edit.go b/src/cmd/go/internal/workcmd/edit.go
index 8d975b0b3d..c1252cc95e 100644
--- a/src/cmd/go/internal/workcmd/edit.go
+++ b/src/cmd/go/internal/workcmd/edit.go
@@ -242,7 +242,10 @@ func allowedVersionArg(arg string) bool {
// parsePathVersionOptional parses path[@version], using adj to
// describe any errors.
func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
- before, after, found := strings.Cut(arg, "@")
+ before, after, found, err := modload.ParsePathVersion(arg)
+ if err != nil {
+ return "", "", err
+ }
if !found {
path = arg
} else {
--
2.35.6