aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/ipc
diff options
context:
space:
mode:
authorAndrei Vagin <avagin@google.com>2022-07-15 16:17:22 -0700
committerDmitry Vyukov <dvyukov@google.com>2022-07-21 09:04:44 +0200
commit6e67af9dcc8b790961e0431f6f9af9511484de90 (patch)
tree06561d94c8ebf183077d3e217627a3d57fb8b442 /pkg/ipc
parent88cb13836210aa2bbaca2dd238b2a2b8c2b851e2 (diff)
pkg/ipc: stop reading executor output after it exited
An executor can leak its file descriptor and we can block on reading from it forever. Signed-off-by: Andrei Vagin <avagin@google.com>
Diffstat (limited to 'pkg/ipc')
-rw-r--r--pkg/ipc/ipc.go20
-rw-r--r--pkg/ipc/ipc_priv_test.go25
2 files changed, 35 insertions, 10 deletions
diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go
index d930b756b..839fdcd4c 100644
--- a/pkg/ipc/ipc.go
+++ b/pkg/ipc/ipc.go
@@ -492,7 +492,7 @@ type command struct {
cmd *exec.Cmd
dir string
readDone chan []byte
- exited chan struct{}
+ exited chan error
inrp *os.File
outwp *os.File
outmem []byte
@@ -603,7 +603,6 @@ func makeCommand(pid int, bin []string, config *Config, inFile, outFile *os.File
c.outwp = outwp
c.readDone = make(chan []byte, 1)
- c.exited = make(chan struct{})
cmd := osutil.Command(bin[0], bin[1:]...)
if inFile != nil && outFile != nil {
@@ -645,7 +644,15 @@ func makeCommand(pid int, bin []string, config *Config, inFile, outFile *os.File
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start executor binary: %v", err)
}
+ c.exited = make(chan error, 1)
c.cmd = cmd
+ go func(c *command) {
+ err := c.cmd.Wait()
+ c.exited <- err
+ close(c.exited)
+ // Avoid a livelock if cmd.Stderr has been leaked to another alive process.
+ rp.SetDeadline(time.Now().Add(5 * time.Second))
+ }(c)
wp.Close()
// Note: we explicitly close inwp before calling handshake even though we defer it above.
// If we don't do it and executor exits before writing handshake reply,
@@ -725,14 +732,7 @@ func (c *command) handshakeError(err error) error {
}
func (c *command) wait() error {
- err := c.cmd.Wait()
- select {
- case <-c.exited:
- // c.exited closed by an earlier call to wait.
- default:
- close(c.exited)
- }
- return err
+ return <-c.exited
}
func (c *command) exec(opts *ExecOpts, progData []byte) (output []byte, hanged bool, err0 error) {
diff --git a/pkg/ipc/ipc_priv_test.go b/pkg/ipc/ipc_priv_test.go
new file mode 100644
index 000000000..be164911a
--- /dev/null
+++ b/pkg/ipc/ipc_priv_test.go
@@ -0,0 +1,25 @@
+// Copyright 2022 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package ipc
+
+import (
+ "testing"
+)
+
+func TestOutputDeadline(t *testing.T) {
+ // Run the command that leaks stderr to a child process.
+ c, err := makeCommand(1, []string{
+ "sh",
+ "-c",
+ "exec 1>&2; ( sleep 100; echo fail ) & echo done",
+ }, &Config{}, nil, nil, nil, "/tmp")
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.wait()
+ out := <-c.readDone
+ if string(out) != "done\n" {
+ t.Errorf("Unexpected output: '%s'", out)
+ }
+}