diff options
| author | Andrei Vagin <avagin@google.com> | 2022-07-15 16:17:22 -0700 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2022-07-21 09:04:44 +0200 |
| commit | 6e67af9dcc8b790961e0431f6f9af9511484de90 (patch) | |
| tree | 06561d94c8ebf183077d3e217627a3d57fb8b442 /pkg/ipc | |
| parent | 88cb13836210aa2bbaca2dd238b2a2b8c2b851e2 (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.go | 20 | ||||
| -rw-r--r-- | pkg/ipc/ipc_priv_test.go | 25 |
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) + } +} |
