diff --git a/container/container.go b/container/container.go index 55e4c44..726a88d 100644 --- a/container/container.go +++ b/container/container.go @@ -25,6 +25,9 @@ const ( // CancelSignal is the signal expected by container init on context cancel. // A custom [Container.Cancel] function must eventually deliver this signal. CancelSignal = SIGUSR2 + + // Timeout from setup pipe creation to when initParams is fully written. + initSetupTimeout = 5 * time.Second ) type ( @@ -228,7 +231,7 @@ func (p *Container) Start() error { } // place setup pipe before user supplied extra files, this is later restored by init - if fd, e, err := Setup(&p.cmd.ExtraFiles); err != nil { + if fd, e, err := Setup(time.Now().Add(initSetupTimeout), &p.cmd.ExtraFiles); err != nil { return &StartError{true, "set up params stream", err, false, false} } else { p.setup = e @@ -324,15 +327,13 @@ func (p *Container) Serve() error { p.SeccompRules = make([]seccomp.NativeRule, 0) } - err := setup.Encode( - &initParams{ - p.Params, - Getuid(), - Getgid(), - len(p.ExtraFiles), - p.msg.IsVerbose(), - }, - ) + err := setup.Encode(&initParams{ + p.Params, + Getuid(), + Getgid(), + len(p.ExtraFiles), + p.msg.IsVerbose(), + }) if err != nil { p.cancel() } diff --git a/container/params.go b/container/params.go index a48eaba..5ab0020 100644 --- a/container/params.go +++ b/container/params.go @@ -6,13 +6,18 @@ import ( "os" "strconv" "syscall" + "time" ) // Setup appends the read end of a pipe for setup params transmission and returns its fd. -func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) { +func Setup(deadline time.Time, extraFiles *[]*os.File) (int, *gob.Encoder, error) { if r, w, err := os.Pipe(); err != nil { return -1, nil, err } else { + if err = w.SetDeadline(deadline); err != nil { + return -1, nil, err + } + fd := 3 + len(*extraFiles) *extraFiles = append(*extraFiles, r) return fd, gob.NewEncoder(w), nil diff --git a/container/params_test.go b/container/params_test.go index a65f686..dbde328 100644 --- a/container/params_test.go +++ b/container/params_test.go @@ -59,7 +59,8 @@ func TestSetupReceive(t *testing.T) { encoderDone := make(chan error, 1) extraFiles := make([]*os.File, 0, 1) - if fd, encoder, err := container.Setup(&extraFiles); err != nil { + deadline, _ := t.Deadline() + if fd, encoder, err := container.Setup(deadline, &extraFiles); err != nil { t.Fatalf("Setup: error = %v", err) } else if fd != 3 { t.Fatalf("Setup: fd = %d, want 3", fd) diff --git a/internal/outcome/process.go b/internal/outcome/process.go index 4ef49b0..3d3d5f1 100644 --- a/internal/outcome/process.go +++ b/internal/outcome/process.go @@ -20,8 +20,12 @@ import ( "hakurei.app/system" ) -// Duration to wait for shim to exit on top of container WaitDelay. -const shimWaitTimeout = 5 * time.Second +const ( + // Duration to wait for shim to exit on top of container WaitDelay. + shimWaitTimeout = 5 * time.Second + // Timeout from setup pipe creation to when outcomeState is fully written. + shimSetupTimeout = 5 * time.Second +) // mainState holds persistent state bound to outcome.main. type mainState struct { @@ -214,7 +218,7 @@ func (k *outcome) main(msg message.Msg) { hsuPath := internal.MustHsuPath() // ms.beforeExit required beyond this point - ms := &mainState{Msg: msg, k: k} + ms := mainState{Msg: msg, k: k} if err := k.sys.Commit(); err != nil { ms.fatal("cannot commit system setup:", err) @@ -232,11 +236,12 @@ func (k *outcome) main(msg message.Msg) { // shim runs in the same session as monitor; see shim.go for behaviour ms.cmd.Cancel = func() error { return ms.cmd.Process.Signal(syscall.SIGCONT) } - var e *gob.Encoder - if fd, encoder, err := container.Setup(&ms.cmd.ExtraFiles); err != nil { + var outcomeStateEncoder *gob.Encoder + if fd, encoder, err := container.Setup(time.Now().Add(shimSetupTimeout), &ms.cmd.ExtraFiles); err != nil { ms.fatal("cannot create shim setup pipe:", err) + panic("unreachable") } else { - e = encoder + outcomeStateEncoder = encoder ms.cmd.Env = []string{ // passed through to shim by hsu shimEnv + "=" + strconv.Itoa(fd), @@ -262,22 +267,9 @@ func (k *outcome) main(msg message.Msg) { go func() { ms.cmdWait <- ms.cmd.Wait(); cancel() }() ms.Time = &startTime - // unfortunately the I/O here cannot be directly canceled; - // the cancellation path leads to fatal in this case so that is fine - select { - case err := <-func() (setupErr chan error) { - setupErr = make(chan error, 1) - go func() { setupErr <- e.Encode(k.state) }() - return - }(): - if err != nil { - msg.Resume() - ms.fatal("cannot transmit shim config:", err) - } - - case <-ctx.Done(): + if err := outcomeStateEncoder.Encode(k.state); err != nil { msg.Resume() - ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err())) + ms.fatal("cannot transmit shim config:", err) } // shim accepted setup payload, create process state