Don't provide IO when it's not set

This makes sure that runc does not get any valid IO for the pipe.  Some
builds and other containers will be stuck if they inspect stdin
expecially and its a pipe but not connected to any user input.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2018-09-07 17:05:33 -04:00
parent b5274fe48a
commit 906acb18b6
10 changed files with 174 additions and 58 deletions

View File

@ -141,6 +141,15 @@ func NewCreator(opts ...Opt) Creator {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if streams.Stdin == nil {
fifos.Stdin = ""
}
if streams.Stdout == nil {
fifos.Stdout = ""
}
if streams.Stderr == nil {
fifos.Stderr = ""
}
return copyIO(fifos, streams) return copyIO(fifos, streams)
} }
} }

View File

@ -23,6 +23,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os/exec" "os/exec"
"runtime" "runtime"
"strings" "strings"
@ -1481,3 +1482,52 @@ func TestBindLowPortNonOpt(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestContainerNoSTDIN(t *testing.T) {
t.Parallel()
client, err := newClient(t, address)
if err != nil {
t.Fatal(err)
}
defer client.Close()
var (
image Image
ctx, cancel = testContext()
id = t.Name()
)
defer cancel()
image, err = client.GetImage(ctx, testImage)
if err != nil {
t.Fatal(err)
}
container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withExitStatus(0)), WithNewSnapshot(id, image))
if err != nil {
t.Fatal(err)
}
defer container.Delete(ctx, WithSnapshotCleanup)
task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStreams(nil, ioutil.Discard, ioutil.Discard)))
if err != nil {
t.Fatal(err)
}
defer task.Delete(ctx)
statusC, err := task.Wait(ctx)
if err != nil {
t.Fatal(err)
}
if err := task.Start(ctx); err != nil {
t.Fatal(err)
}
status := <-statusC
code, _, err := status.Result()
if err != nil {
t.Fatal(err)
}
if code != 0 {
t.Errorf("expected status 0 from wait but received %d", code)
}
}

View File

@ -147,7 +147,7 @@ func (e *execProcess) start(ctx context.Context) (err error) {
return errors.Wrap(err, "creating new NULL IO") return errors.Wrap(err, "creating new NULL IO")
} }
} else { } else {
if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID); err != nil { if e.io, err = runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio)); err != nil {
return errors.Wrap(err, "failed to create runc io pipes") return errors.Wrap(err, "failed to create runc io pipes")
} }
} }

View File

@ -123,7 +123,7 @@ func (p *Init) Create(ctx context.Context, r *CreateConfig) error {
return errors.Wrap(err, "creating new NULL IO") return errors.Wrap(err, "creating new NULL IO")
} }
} else { } else {
if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID); err != nil { if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID, withConditionalIO(p.stdio)); err != nil {
return errors.Wrap(err, "failed to create OCI runtime io pipes") return errors.Wrap(err, "failed to create OCI runtime io pipes")
} }
} }
@ -399,3 +399,11 @@ func (p *Init) runtimeError(rErr error, msg string) error {
return errors.Errorf("%s: %s", msg, rMsg) return errors.Errorf("%s: %s", msg, rMsg)
} }
} }
func withConditionalIO(c proc.Stdio) runc.IOOpt {
return func(o *runc.IOOption) {
o.OpenStdin = c.Stdin != ""
o.OpenStdout = c.Stdout != ""
o.OpenStderr = c.Stderr != ""
}
}

View File

@ -109,7 +109,6 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
i.dest(fw, fr) i.dest(fw, fr)
} }
if stdin == "" { if stdin == "" {
rio.Stdin().Close()
return nil return nil
} }
f, err := fifo.OpenFifo(ctx, stdin, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) f, err := fifo.OpenFifo(ctx, stdin, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)

View File

@ -1,4 +1,4 @@
github.com/containerd/go-runc acb7c88cac264acca9b5eae187a117f4d77a1292 github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40

View File

@ -34,6 +34,24 @@ type StartCloser interface {
CloseAfterStart() error CloseAfterStart() error
} }
// IOOpt sets I/O creation options
type IOOpt func(*IOOption)
// IOOption holds I/O creation options
type IOOption struct {
OpenStdin bool
OpenStdout bool
OpenStderr bool
}
func defaultIOOption() *IOOption {
return &IOOption{
OpenStdin: true,
OpenStdout: true,
OpenStderr: true,
}
}
func newPipe() (*pipe, error) { func newPipe() (*pipe, error) {
r, w, err := os.Pipe() r, w, err := os.Pipe()
if err != nil { if err != nil {
@ -65,14 +83,23 @@ type pipeIO struct {
} }
func (i *pipeIO) Stdin() io.WriteCloser { func (i *pipeIO) Stdin() io.WriteCloser {
if i.in == nil {
return nil
}
return i.in.w return i.in.w
} }
func (i *pipeIO) Stdout() io.ReadCloser { func (i *pipeIO) Stdout() io.ReadCloser {
if i.out == nil {
return nil
}
return i.out.r return i.out.r
} }
func (i *pipeIO) Stderr() io.ReadCloser { func (i *pipeIO) Stderr() io.ReadCloser {
if i.err == nil {
return nil
}
return i.err.r return i.err.r
} }
@ -83,28 +110,38 @@ func (i *pipeIO) Close() error {
i.out, i.out,
i.err, i.err,
} { } {
if cerr := v.Close(); err == nil { if v != nil {
err = cerr if cerr := v.Close(); err == nil {
err = cerr
}
} }
} }
return err return err
} }
func (i *pipeIO) CloseAfterStart() error { func (i *pipeIO) CloseAfterStart() error {
for _, f := range []*os.File{ for _, f := range []*pipe{
i.out.w, i.out,
i.err.w, i.err,
} { } {
f.Close() if f != nil {
f.w.Close()
}
} }
return nil return nil
} }
// Set sets the io to the exec.Cmd // Set sets the io to the exec.Cmd
func (i *pipeIO) Set(cmd *exec.Cmd) { func (i *pipeIO) Set(cmd *exec.Cmd) {
cmd.Stdin = i.in.r if i.in != nil {
cmd.Stdout = i.out.w cmd.Stdin = i.in.r
cmd.Stderr = i.err.w }
if i.out != nil {
cmd.Stdout = i.out.w
}
if i.err != nil {
cmd.Stderr = i.err.w
}
} }
func NewSTDIO() (IO, error) { func NewSTDIO() (IO, error) {

View File

@ -24,8 +24,15 @@ import (
) )
// NewPipeIO creates pipe pairs to be used with runc // NewPipeIO creates pipe pairs to be used with runc
func NewPipeIO(uid, gid int) (i IO, err error) { func NewPipeIO(uid, gid int, opts ...IOOpt) (i IO, err error) {
var pipes []*pipe option := defaultIOOption()
for _, o := range opts {
o(option)
}
var (
pipes []*pipe
stdin, stdout, stderr *pipe
)
// cleanup in case of an error // cleanup in case of an error
defer func() { defer func() {
if err != nil { if err != nil {
@ -34,33 +41,33 @@ func NewPipeIO(uid, gid int) (i IO, err error) {
} }
} }
}() }()
stdin, err := newPipe() if option.OpenStdin {
if err != nil { if stdin, err = newPipe(); err != nil {
return nil, err return nil, err
}
pipes = append(pipes, stdin)
if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stdin")
}
} }
pipes = append(pipes, stdin) if option.OpenStdout {
if err = unix.Fchown(int(stdin.r.Fd()), uid, gid); err != nil { if stdout, err = newPipe(); err != nil {
return nil, errors.Wrap(err, "failed to chown stdin") return nil, err
}
pipes = append(pipes, stdout)
if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stdout")
}
} }
if option.OpenStderr {
stdout, err := newPipe() if stderr, err = newPipe(); err != nil {
if err != nil { return nil, err
return nil, err }
pipes = append(pipes, stderr)
if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stderr")
}
} }
pipes = append(pipes, stdout)
if err = unix.Fchown(int(stdout.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stdout")
}
stderr, err := newPipe()
if err != nil {
return nil, err
}
pipes = append(pipes, stderr)
if err = unix.Fchown(int(stderr.w.Fd()), uid, gid); err != nil {
return nil, errors.Wrap(err, "failed to chown stderr")
}
return &pipeIO{ return &pipeIO{
in: stdin, in: stdin,
out: stdout, out: stdout,

View File

@ -19,8 +19,15 @@
package runc package runc
// NewPipeIO creates pipe pairs to be used with runc // NewPipeIO creates pipe pairs to be used with runc
func NewPipeIO() (i IO, err error) { func NewPipeIO(opts ...IOOpt) (i IO, err error) {
var pipes []*pipe option := defaultIOOption()
for _, o := range opts {
o(option)
}
var (
pipes []*pipe
stdin, stdout, stderr *pipe
)
// cleanup in case of an error // cleanup in case of an error
defer func() { defer func() {
if err != nil { if err != nil {
@ -29,24 +36,24 @@ func NewPipeIO() (i IO, err error) {
} }
} }
}() }()
stdin, err := newPipe() if option.OpenStdin {
if err != nil { if stdin, err = newPipe(); err != nil {
return nil, err return nil, err
}
pipes = append(pipes, stdin)
} }
pipes = append(pipes, stdin) if option.OpenStdout {
if stdout, err = newPipe(); err != nil {
stdout, err := newPipe() return nil, err
if err != nil { }
return nil, err pipes = append(pipes, stdout)
} }
pipes = append(pipes, stdout) if option.OpenStderr {
if stderr, err = newPipe(); err != nil {
stderr, err := newPipe() return nil, err
if err != nil { }
return nil, err pipes = append(pipes, stderr)
} }
pipes = append(pipes, stderr)
return &pipeIO{ return &pipeIO{
in: stdin, in: stdin,
out: stdout, out: stdout,

View File

@ -608,9 +608,8 @@ func parseVersion(data []byte) (Version, error) {
var v Version var v Version
parts := strings.Split(strings.TrimSpace(string(data)), "\n") parts := strings.Split(strings.TrimSpace(string(data)), "\n")
if len(parts) != 3 { if len(parts) != 3 {
return v, ErrParseRuncVersion return v, nil
} }
for i, p := range []struct { for i, p := range []struct {
dest *string dest *string
split string split string