From 6c3b5a2798c83d583cb37dba9f39c47300d19f1f Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Wed, 2 Jul 2025 23:26:44 -0400 Subject: runtime: correct vdsoSP on S390X It should get the caller's SP. The current code gets the address of the first parameter, which is one word above the caller's SP. There is a slot for saving the LR at 0(SP) in the caller's frame. Fixes #62086 (for s390x). Change-Id: Ie8cbfabc8161b98658c884a32e0af72df189ea56 Reviewed-on: https://go-review.googlesource.com/c/go/+/685715 Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI --- src/runtime/sys_linux_s390x.s | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/runtime') diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s index 2f9d4beda8..a3472a4508 100644 --- a/src/runtime/sys_linux_s390x.s +++ b/src/runtime/sys_linux_s390x.s @@ -226,7 +226,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$32-12 MOVD R4, 24(R15) MOVD R14, R8 // Backup return address - MOVD $sec+0(FP), R4 // return parameter caller + MOVD $ret-8(FP), R4 // caller's SP MOVD R8, m_vdsoPC(R6) MOVD R4, m_vdsoSP(R6) @@ -312,7 +312,7 @@ TEXT runtime·nanotime1(SB),NOSPLIT,$32-8 MOVD R4, 24(R15) MOVD R14, R8 // Backup return address - MOVD $ret+0(FP), R4 // caller's SP + MOVD $ret-8(FP), R4 // caller's SP MOVD R8, m_vdsoPC(R6) MOVD R4, m_vdsoSP(R6) -- cgit v1.3-5-g9baa From 75b43f9a97ada1f0fce7a5775d4ab373bd0dad9c Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Fri, 27 Jun 2025 16:40:43 +0000 Subject: runtime: make traceStack testable and add a benchmark Change-Id: Ide4daa5eee3fd4f3007d6ef23aa84b8916562c39 Reviewed-on: https://go-review.googlesource.com/c/go/+/684457 Reviewed-by: Cherry Mui Auto-Submit: Michael Knyszek LUCI-TryBot-Result: Go LUCI --- src/runtime/export_test.go | 10 +++++++++ src/runtime/symtab.go | 5 ++++- src/runtime/trace.go | 2 +- src/runtime/traceevent.go | 2 +- src/runtime/tracestack.go | 8 +++----- src/runtime/tracestack_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 src/runtime/tracestack_test.go (limited to 'src/runtime') diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 83cf301be4..9a4611e26e 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -1917,3 +1917,13 @@ const ( BubbleAssocCurrentBubble = bubbleAssocCurrentBubble BubbleAssocOtherBubble = bubbleAssocOtherBubble ) + +type TraceStackTable traceStackTable + +func (t *TraceStackTable) Reset() { + t.tab.reset() +} + +func TraceStack(gp *G, tab *TraceStackTable) { + traceStack(0, gp, (*traceStackTable)(tab)) +} diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index 8c6ef2b4fc..866c46a83d 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -981,6 +981,9 @@ func pcvalue(f funcInfo, off uint32, targetpc uintptr, strict bool) (int32, uint // matches the cached contents. const debugCheckCache = false + // If true, skip checking the cache entirely. + const skipCache = false + if off == 0 { return -1, 0 } @@ -991,7 +994,7 @@ func pcvalue(f funcInfo, off uint32, targetpc uintptr, strict bool) (int32, uint var checkVal int32 var checkPC uintptr ck := pcvalueCacheKey(targetpc) - { + if !skipCache { mp := acquirem() cache := &mp.pcvalueCache // The cache can be used by the signal handler on this M. Avoid diff --git a/src/runtime/trace.go b/src/runtime/trace.go index b92e7b4e8e..0d71ad445c 100644 --- a/src/runtime/trace.go +++ b/src/runtime/trace.go @@ -396,7 +396,7 @@ func traceAdvance(stopTrace bool) { ug.status = readgstatus(s.g) &^ _Gscan ug.waitreason = s.g.waitreason ug.inMarkAssist = s.g.inMarkAssist - ug.stackID = traceStack(0, gp, gen) + ug.stackID = traceStack(0, gp, &trace.stackTab[gen%2]) } resumeG(s) casgstatus(me, _Gwaiting, _Grunning) diff --git a/src/runtime/traceevent.go b/src/runtime/traceevent.go index 9d1a93d3f9..263847be2e 100644 --- a/src/runtime/traceevent.go +++ b/src/runtime/traceevent.go @@ -56,7 +56,7 @@ func (e traceEventWriter) event(ev tracev2.EventType, args ...traceArg) { // It then returns a traceArg representing that stack which may be // passed to write. func (tl traceLocker) stack(skip int) traceArg { - return traceArg(traceStack(skip, nil, tl.gen)) + return traceArg(traceStack(skip, nil, &trace.stackTab[tl.gen%2])) } // startPC takes a start PC for a goroutine and produces a unique diff --git a/src/runtime/tracestack.go b/src/runtime/tracestack.go index 2ee68c85f0..76d6b05048 100644 --- a/src/runtime/tracestack.go +++ b/src/runtime/tracestack.go @@ -28,10 +28,8 @@ const ( // skip controls the number of leaf frames to omit in order to hide tracer internals // from stack traces, see CL 5523. // -// Avoid calling this function directly. gen needs to be the current generation -// that this stack trace is being written out for, which needs to be synchronized with -// generations moving forward. Prefer traceEventWriter.stack. -func traceStack(skip int, gp *g, gen uintptr) uint64 { +// Avoid calling this function directly. Prefer traceEventWriter.stack. +func traceStack(skip int, gp *g, tab *traceStackTable) uint64 { var pcBuf [tracev2.MaxFramesPerStack]uintptr // Figure out gp and mp for the backtrace. @@ -134,7 +132,7 @@ func traceStack(skip int, gp *g, gen uintptr) uint64 { if nstk > 0 && gp.goid == 1 { nstk-- // skip runtime.main } - id := trace.stackTab[gen%2].put(pcBuf[:nstk]) + id := tab.put(pcBuf[:nstk]) return id } diff --git a/src/runtime/tracestack_test.go b/src/runtime/tracestack_test.go new file mode 100644 index 0000000000..eaf4d906e3 --- /dev/null +++ b/src/runtime/tracestack_test.go @@ -0,0 +1,46 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runtime_test + +import ( + "runtime" + "strconv" + "testing" +) + +func BenchmarkTraceStack(b *testing.B) { + for _, stackDepth := range []int{1, 10, 100} { + b.Run("stackDepth="+strconv.Itoa(stackDepth), func(b *testing.B) { + benchmarkTraceStack(b, stackDepth) + }) + } +} + +func benchmarkTraceStack(b *testing.B, stackDepth int) { + var tab runtime.TraceStackTable + defer tab.Reset() + + wait := make(chan struct{}) + ready := make(chan struct{}) + done := make(chan struct{}) + var gp *runtime.G + go func() { + gp = runtime.Getg() + useStackAndCall(stackDepth, func() { + ready <- struct{}{} + <-wait + }) + done <- struct{}{} + }() + <-ready + + for b.Loop() { + runtime.TraceStack(gp, &tab) + } + + // Clean up. + wait <- struct{}{} + <-done +} -- cgit v1.3-5-g9baa From 54c9d776302d53ab1907645cb67fa4a948e1500c Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Mon, 9 Jun 2025 11:23:46 -0700 Subject: cmd/go: disable support for multiple vcs in one module Removes the somewhat redundant vcs.FromDir, "allowNesting" argument, which was always enabled, and disallow multiple VCS metadata folders being present in a single directory. This makes VCS injection attacks much more difficult. Also adds a GODEBUG, allowmultiplevcs, which re-enables this behavior. Thanks to RyotaK (https://ryotak.net) of GMO Flatt Security Inc for reporting this issue. Fixes #74380 Fixes CVE-2025-4674 Change-Id: I5787d90cdca8deb3aca6f154efb627df1e7d2789 Reviewed-on: https://go-review.googlesource.com/c/go/+/686515 LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase Commit-Queue: Carlos Amedee Reviewed-by: Carlos Amedee --- doc/godebug.md | 5 ++ src/cmd/go/internal/load/pkg.go | 14 +++--- src/cmd/go/internal/modfetch/repo.go | 2 +- src/cmd/go/internal/vcs/vcs.go | 28 +++++++---- src/cmd/go/internal/vcs/vcs_test.go | 2 +- src/cmd/go/testdata/script/test_multivcs.txt | 54 ++++++++++++++++++++++ .../go/testdata/script/version_buildvcs_nested.txt | 20 ++++++-- src/internal/godebugs/table.go | 1 + src/runtime/metrics/doc.go | 5 ++ 9 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 src/cmd/go/testdata/script/test_multivcs.txt (limited to 'src/runtime') diff --git a/doc/godebug.md b/doc/godebug.md index d107b1baf1..aaa0f9dd55 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -189,6 +189,11 @@ crypto/x509.CreateCertificate. The setting `x509sha256skid=0` reverts to SHA-1. Go 1.25 corrected the semantics of contention reports for runtime-internal locks, and so removed the [`runtimecontentionstacks` setting](/pkg/runtime#hdr-Environment_Variables). +Go 1.25 (starting with Go 1.25 RC 2) disabled build information stamping when +multiple VCS are detected due to concerns around VCS injection attacks. This +behavior and setting was backported to Go 1.24.5 and Go 1.23.11. This behavior +can be renabled with the setting `allowmultiplevcs=1`. + ### Go 1.24 Go 1.24 added a new `fips140` setting that controls whether the Go diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index e913f98852..1f791546f9 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -2496,7 +2496,6 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { var repoDir string var vcsCmd *vcs.Cmd var err error - const allowNesting = true wantVCS := false switch cfg.BuildBuildvcs { @@ -2516,7 +2515,7 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { // (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 @@ -2539,10 +2538,11 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { } 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 @@ -2554,7 +2554,7 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { } goto omitVCS } - modRepoDir, _, err := vcs.FromDir(p.Module.Dir, "", allowNesting) + modRepoDir, _, err := vcs.FromDir(p.Module.Dir, "") if err != nil { setVCSError(err) return diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go index 0fdb2a8736..5d4d679e83 100644 --- a/src/cmd/go/internal/modfetch/repo.go +++ b/src/cmd/go/internal/modfetch/repo.go @@ -235,7 +235,7 @@ func LookupLocal(ctx context.Context, codeRoot string, path string, dir string) return lookupLocalCache.Do(path, func() Repo { return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) { - repoDir, vcsCmd, err := vcs.FromDir(dir, "", true) + repoDir, vcsCmd, err := vcs.FromDir(dir, "") if err != nil { return nil, err } diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go index ebcb2efb34..7e081eb41a 100644 --- a/src/cmd/go/internal/vcs/vcs.go +++ b/src/cmd/go/internal/vcs/vcs.go @@ -8,6 +8,7 @@ import ( "bytes" "errors" "fmt" + "internal/godebug" "internal/lazyregexp" "internal/singleflight" "io/fs" @@ -869,11 +870,13 @@ type vcsPath struct { 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 != "" { @@ -887,21 +890,28 @@ func FromDir(dir, srcRoot string, allowNesting bool) (repoDir string, vcsCmd *Cm 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) } } diff --git a/src/cmd/go/internal/vcs/vcs_test.go b/src/cmd/go/internal/vcs/vcs_test.go index c143154948..361d85bcfb 100644 --- a/src/cmd/go/internal/vcs/vcs_test.go +++ b/src/cmd/go/internal/vcs/vcs_test.go @@ -239,7 +239,7 @@ func TestFromDir(t *testing.T) { } 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 diff --git a/src/cmd/go/testdata/script/test_multivcs.txt b/src/cmd/go/testdata/script/test_multivcs.txt new file mode 100644 index 0000000000..538cbf700b --- /dev/null +++ b/src/cmd/go/testdata/script/test_multivcs.txt @@ -0,0 +1,54 @@ +# 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 diff --git a/src/cmd/go/testdata/script/version_buildvcs_nested.txt b/src/cmd/go/testdata/script/version_buildvcs_nested.txt index 6dab8474b5..22cd71c454 100644 --- a/src/cmd/go/testdata/script/version_buildvcs_nested.txt +++ b/src/cmd/go/testdata/script/version_buildvcs_nested.txt @@ -9,25 +9,35 @@ cd root 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 diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 38dc7b0fac..2d00882545 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -26,6 +26,7 @@ type Info struct { // 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: "containermaxprocs", Package: "runtime", Changed: 25, Old: "0"}, {Name: "dataindependenttiming", Package: "crypto/subtle", Opaque: true}, diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 32fc436e1a..a1902bc6d7 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -230,6 +230,11 @@ Below is the full list of supported metrics, ordered lexicographically. /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. -- cgit v1.3-5-g9baa From 8131635e5a9c7ae2fd2c083bed9e841d27226500 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Wed, 9 Jul 2025 15:50:06 +0000 Subject: runtime: run TestSignalDuringExec in its own process group TestSignalDuringExec sends a SIGWINCH to the whole process group. However, it may execute concurrently with other copies of the runtime tests, especially through `go tool dist`, and gdb version <12.1 has a bug in non-interactive mode where recieving a SIGWINCH causes a crash. This change modifies SignalDuringExec in the testprog to first fork itself into a new process group. To avoid issues with Ctrl+C and the new process group hanging, the new process blocks on a pipe that is passed down to it. This pipe is automatically closed when its parent exits, which should ensure that the subprocess also exits. Fixes #58932. Change-Id: I3906afa28cf8b15d22ae612d071bce7f30fc3e6c Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest-noswissmap,gotip-linux-amd64-longtest-aliastypeparams,gotip-linux-amd64-longtest,gotip-linux-386-longtest Reviewed-on: https://go-review.googlesource.com/c/go/+/686875 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt --- src/runtime/runtime-gdb_test.go | 3 ++ src/runtime/testdata/testprognet/signalexec.go | 43 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) (limited to 'src/runtime') diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index 19ad29c127..47c1fe5851 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -78,6 +78,9 @@ func checkGdbVersion(t *testing.T) { if major < 10 { t.Skipf("skipping: gdb version %d.%d too old", major, minor) } + if major < 12 || (major == 12 && minor < 1) { + t.Logf("gdb version <12.1 is known to crash due to a SIGWINCH recieved in non-interactive mode; if you see a crash, some test may be sending SIGWINCH to the whole process group. See go.dev/issue/58932.") + } t.Logf("gdb version %d.%d", major, minor) } diff --git a/src/runtime/testdata/testprognet/signalexec.go b/src/runtime/testdata/testprognet/signalexec.go index 62ebce7176..7e7591e8f7 100644 --- a/src/runtime/testdata/testprognet/signalexec.go +++ b/src/runtime/testdata/testprognet/signalexec.go @@ -13,9 +13,11 @@ package main import ( "fmt" + "io" "os" "os/exec" "os/signal" + "runtime" "sync" "syscall" "time" @@ -23,10 +25,51 @@ import ( func init() { register("SignalDuringExec", SignalDuringExec) + register("SignalDuringExecPgrp", SignalDuringExecPgrp) register("Nop", Nop) } func SignalDuringExec() { + // Re-launch ourselves in a new process group. + cmd := exec.Command(os.Args[0], "SignalDuringExecPgrp") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + // Start the new process with an extra pipe. It will + // exit if the pipe is closed. + rp, wp, err := os.Pipe() + if err != nil { + fmt.Printf("Failed to create pipe: %v", err) + return + } + cmd.ExtraFiles = []*os.File{rp} + + // Run the command. + if err := cmd.Run(); err != nil { + fmt.Printf("Run failed: %v", err) + } + + // We don't actually need to write to the pipe, it just + // needs to get closed, which will happen on process + // exit. + runtime.KeepAlive(wp) +} + +func SignalDuringExecPgrp() { + // Grab fd 3 which is a pipe we need to read on. + f := os.NewFile(3, "pipe") + go func() { + // Nothing will ever get written to the pipe, so we'll + // just block on it. If it closes, ReadAll will return + // one way or another, at which point we'll exit. + io.ReadAll(f) + os.Exit(1) + }() + + // This is just for SignalDuringExec. pgrp := syscall.Getpgrp() const tries = 10 -- cgit v1.3-5-g9baa