the [`httpservecontentkeepheaders` setting](/pkg/net/http#ServeContent).
Using `httpservecontentkeepheaders=1` restores the pre-Go 1.23 behavior.
+Go 1.23.11 disabled build information stamping when multiple VCS are detected due
+to concerns around VCS injection attacks. This behavior can be renabled with the
+setting `allowmultiplevcs=1`.
+
### Go 1.22
Go 1.22 adds a configurable limit to control the maximum acceptable RSA key size
var repoDir string
var vcsCmd *vcs.Cmd
var err error
- const allowNesting = true
wantVCS := false
switch cfg.BuildBuildvcs {
// (so the bootstrap toolchain packages don't even appear to be in GOROOT).
goto omitVCS
}
- repoDir, vcsCmd, err = vcs.FromDir(base.Cwd(), "", allowNesting)
+ repoDir, vcsCmd, err = vcs.FromDir(base.Cwd(), "")
if err != nil && !errors.Is(err, os.ErrNotExist) {
setVCSError(err)
return
}
if repoDir != "" && vcsCmd.Status != nil {
// Check that the current directory, package, and module are in the same
- // repository. vcs.FromDir allows nested Git repositories, but nesting
- // is not allowed for other VCS tools. The current directory may be outside
- // p.Module.Dir when a workspace is used.
- pkgRepoDir, _, err := vcs.FromDir(p.Dir, "", allowNesting)
+ // repository. vcs.FromDir disallows nested VCS and multiple VCS in the
+ // same repository, unless the GODEBUG allowmultiplevcs is set. The
+ // current directory may be outside p.Module.Dir when a workspace is
+ // used.
+ pkgRepoDir, _, err := vcs.FromDir(p.Dir, "")
if err != nil {
setVCSError(err)
return
}
goto omitVCS
}
- modRepoDir, _, err := vcs.FromDir(p.Module.Dir, "", allowNesting)
+ modRepoDir, _, err := vcs.FromDir(p.Module.Dir, "")
if err != nil {
setVCSError(err)
return
"bytes"
"errors"
"fmt"
+ "internal/godebug"
"internal/lazyregexp"
"internal/singleflight"
"io/fs"
schemelessRepo bool // if true, the repo pattern lacks a scheme
}
+var allowmultiplevcs = godebug.New("allowmultiplevcs")
+
// FromDir inspects dir and its parents to determine the
// version control system and code repository to use.
// If no repository is found, FromDir returns an error
// equivalent to os.ErrNotExist.
-func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cmd, err error) {
+func FromDir(dir, srcRoot string) (repoDir string, vcsCmd *Cmd, err error) {
// Clean and double-check that dir is in (a subdirectory of) srcRoot.
dir = filepath.Clean(dir)
if srcRoot != "" {
for len(dir) > len(srcRoot) {
for _, vcs := range vcsList {
if isVCSRoot(dir, vcs.RootNames) {
- // Record first VCS we find.
- // If allowNesting is false (as it is in GOPATH), keep looking for
- // repositories in parent directories and report an error if one is
- // found to mitigate VCS injection attacks.
if vcsCmd == nil {
+ // Record first VCS we find.
vcsCmd = vcs
repoDir = dir
- if allowNesting {
+ if allowmultiplevcs.Value() == "1" {
+ allowmultiplevcs.IncNonDefault()
return repoDir, vcsCmd, nil
}
+ // If allowmultiplevcs is not set, keep looking for
+ // repositories in current and parent directories and report
+ // an error if one is found to mitigate VCS injection
+ // attacks.
+ continue
+ }
+ if vcsCmd == vcsGit && vcs == vcsGit {
+ // Nested Git is allowed, as this is how things like
+ // submodules work. Git explicitly protects against
+ // injection against itself.
continue
}
- // Otherwise, we have one VCS inside a different VCS.
- return "", nil, fmt.Errorf("directory %q uses %s, but parent %q uses %s",
- repoDir, vcsCmd.Cmd, dir, vcs.Cmd)
+ return "", nil, fmt.Errorf("multiple VCS detected: %s in %q, and %s in %q",
+ vcsCmd.Cmd, repoDir, vcs.Cmd, dir)
}
}
}
wantRepoDir := filepath.Dir(dir)
- gotRepoDir, gotVCS, err := FromDir(dir, tempDir, false)
+ gotRepoDir, gotVCS, err := FromDir(dir, tempDir)
if err != nil {
t.Errorf("FromDir(%q, %q): %v", dir, tempDir, err)
continue
--- /dev/null
+# To avoid VCS injection attacks, we should not accept multiple different VCS metadata
+# folders within a single module (either in the same directory, or nested in different
+# directories.)
+#
+# This behavior should be disabled by setting the allowmultiplevcs GODEBUG.
+
+[short] skip
+[!git] skip
+
+cd samedir
+
+exec git init .
+
+# Without explicitly requesting buildvcs, the go command should silently continue
+# without determining the correct VCS.
+go test -c -o $devnull .
+
+# If buildvcs is explicitly requested, we expect the go command to fail
+! go test -buildvcs -c -o $devnull .
+stderr '^error obtaining VCS status: multiple VCS detected:'
+
+env GODEBUG=allowmultiplevcs=1
+go test -buildvcs -c -o $devnull .
+
+env GODEBUG=
+cd ../nested
+exec git init .
+# cd a
+go test -c -o $devnull ./a
+! go test -buildvcs -c -o $devnull ./a
+stderr '^error obtaining VCS status: multiple VCS detected:'
+# allowmultiplevcs doesn't disable the check that the current directory, package, and
+# module are in the same repository.
+env GODEBUG=allowmultiplevcs=1
+! go test -buildvcs -c -o $devnull ./a
+stderr '^error obtaining VCS status: main package is in repository'
+
+-- samedir/go.mod --
+module example
+
+go 1.18
+-- samedir/example.go --
+package main
+-- samedir/.bzr/test --
+hello
+
+-- nested/go.mod --
+module example
+
+go 1.18
+-- nested/a/example.go --
+package main
+-- nested/a/.bzr/test --
+hello
go mod init example.com/root
exec git init
-# Nesting repositories in parent directories are ignored, as the current
-# directory main package, and containing main module are in the same repository.
-# This is an error in GOPATH mode (to prevent VCS injection), but for modules,
-# we assume users have control over repositories they've checked out.
+
+# Nesting repositories in parent directories are an error, to prevent VCS injection.
+# This can be disabled with the allowmultiplevcs GODEBUG.
mkdir hgsub
cd hgsub
exec hg init
cp ../../main.go main.go
! go build
+stderr '^error obtaining VCS status: multiple VCS detected: hg in ".*hgsub", and git in ".*root"$'
+stderr '^\tUse -buildvcs=false to disable VCS stamping.$'
+env GODEBUG=allowmultiplevcs=1
+! go build
stderr '^error obtaining VCS status: main module is in repository ".*root" but current directory is in repository ".*hgsub"$'
stderr '^\tUse -buildvcs=false to disable VCS stamping.$'
go build -buildvcs=false
+env GODEBUG=
go mod init example.com/root/hgsub
+! go build
+stderr '^error obtaining VCS status: multiple VCS detected: hg in ".*hgsub", and git in ".*root"$'
+stderr '^\tUse -buildvcs=false to disable VCS stamping.$'
+env GODEBUG=allowmultiplevcs=1
go build
+env GODEBUG=
cd ..
# It's an error to build a package from a nested Git repository if the package
# is in a separate repository from the current directory or from the module
-# root directory.
+# root directory. Otherwise nested Git repositories are allowed, as this is
+# how Git implements submodules (and protects against Git based VCS injection.)
mkdir gitsub
cd gitsub
exec git init
if info.Old != "" && info.Changed == 0 {
t.Errorf("Name=%s has Old, missing Changed", info.Name)
}
- if !strings.Contains(doc, "`"+info.Name+"`") {
+ if !strings.Contains(doc, "`"+info.Name+"`") &&
+ !strings.Contains(doc, "`"+info.Name+"=") {
t.Errorf("Name=%s not documented in doc/godebug.md", info.Name)
}
if !info.Opaque && !incs[info.Name] {
// Note: After adding entries to this table, update the list in doc/godebug.md as well.
// (Otherwise the test in this package will fail.)
var All = []Info{
+ {Name: "allowmultiplevcs", Package: "cmd/go"},
{Name: "asynctimerchan", Package: "time", Changed: 23, Old: "1"},
{Name: "execerrdot", Package: "os/exec"},
{Name: "gocachehash", Package: "cmd/go"},
/gc/stack/starting-size:bytes
The stack size of new goroutines.
+ /godebug/non-default-behavior/allowmultiplevcs:events
+ The number of non-default behaviors executed by the cmd/go
+ package due to a non-default GODEBUG=allowmultiplevcs=...
+ setting.
+
/godebug/non-default-behavior/asynctimerchan:events
The number of non-default behaviors executed by the time package
due to a non-default GODEBUG=asynctimerchan=... setting.