diff --git a/cmd/sharefs/fuse.go b/cmd/sharefs/fuse.go index 1dc34e21..c9855eb7 100644 --- a/cmd/sharefs/fuse.go +++ b/cmd/sharefs/fuse.go @@ -19,7 +19,6 @@ import ( "encoding/gob" "errors" "fmt" - "io" "log" "os" "os/exec" @@ -470,13 +469,14 @@ func _main(s ...string) (exitCode int) { os.NewFile(uintptr(C.fuse_session_fd(se)), "fuse"), )) - var setupWriter io.WriteCloser - if fd, w, err := container.Setup(&z.ExtraFiles); err != nil { + var setupPipe [2]*os.File + if r, w, err := os.Pipe(); err != nil { log.Println(err) return 5 } else { - z.Args = append(z.Args, "-osetup="+strconv.Itoa(fd)) - setupWriter = w + z.Args = append(z.Args, "-osetup="+strconv.Itoa(3+len(z.ExtraFiles))) + z.ExtraFiles = append(z.ExtraFiles, r) + setupPipe[0], setupPipe[1] = r, w } if err := z.Start(); err != nil { @@ -487,6 +487,9 @@ func _main(s ...string) (exitCode int) { } return 5 } + if err := setupPipe[0].Close(); err != nil { + log.Println(err) + } if err := z.Serve(); err != nil { if m, ok := message.GetMessage(err); ok { log.Println(m) @@ -496,10 +499,10 @@ func _main(s ...string) (exitCode int) { return 5 } - if err := gob.NewEncoder(setupWriter).Encode(&setup); err != nil { + if err := gob.NewEncoder(setupPipe[1]).Encode(&setup); err != nil { log.Println(err) return 5 - } else if err = setupWriter.Close(); err != nil { + } else if err = setupPipe[1].Close(); err != nil { log.Println(err) } diff --git a/container/container.go b/container/container.go index 480452cd..d45d7a97 100644 --- a/container/container.go +++ b/container/container.go @@ -53,7 +53,7 @@ type ( ExtraFiles []*os.File // Write end of a pipe connected to the init to deliver [Params]. - setup *os.File + setup [2]*os.File // Cancels the context passed to the underlying cmd. cancel context.CancelFunc // Closed after Wait returns. Keeps the spawning thread alive. @@ -287,14 +287,16 @@ func (p *Container) Start() error { } // place setup pipe before user supplied extra files, this is later restored by init - if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil { + if r, w, err := os.Pipe(); err != nil { return &StartError{ Fatal: true, Step: "set up params stream", Err: err, } } else { - p.setup = f + fd := 3 + len(p.cmd.ExtraFiles) + p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, r) + p.setup[0], p.setup[1] = r, w p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)} } p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...) @@ -428,14 +430,30 @@ func (p *Container) Start() error { // Serve serves [Container.Params] to the container init. // // Serve must only be called once. -func (p *Container) Serve() error { - if p.setup == nil { +func (p *Container) Serve() (err error) { + if p.setup[0] == nil || p.setup[1] == nil { panic("invalid serve") } + defer func() { + if closeErr := p.setup[1].Close(); err == nil { + err = closeErr + } - setup := p.setup - p.setup = nil - if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil { + if err != nil { + p.cancel() + } + p.setup[0], p.setup[1] = nil, nil + }() + if err = p.setup[0].Close(); err != nil { + return &StartError{ + Fatal: true, + Step: "close read end of init pipe", + Err: err, + Passthrough: true, + } + } + + if err = p.setup[1].SetDeadline(time.Now().Add(initSetupTimeout)); err != nil { return &StartError{ Fatal: true, Step: "set init pipe deadline", @@ -445,7 +463,6 @@ func (p *Container) Serve() error { } if p.Path == nil { - p.cancel() return &StartError{ Step: "invalid executable pathname", Err: EINVAL, @@ -461,18 +478,13 @@ func (p *Container) Serve() error { p.SeccompRules = make([]std.NativeRule, 0) } - err := gob.NewEncoder(setup).Encode(&initParams{ + return gob.NewEncoder(p.setup[1]).Encode(&initParams{ p.Params, Getuid(), Getgid(), len(p.ExtraFiles), p.msg.IsVerbose(), }) - _ = setup.Close() - if err != nil { - p.cancel() - } - return err } // Wait blocks until the container init process to exit and releases any diff --git a/container/params.go b/container/params.go index 46c3b9b2..e6cd26f8 100644 --- a/container/params.go +++ b/container/params.go @@ -8,17 +8,6 @@ import ( "syscall" ) -// Setup appends the read end of a pipe for setup params transmission and returns its fd. -func Setup(extraFiles *[]*os.File) (int, *os.File, error) { - if r, w, err := os.Pipe(); err != nil { - return -1, nil, err - } else { - fd := 3 + len(*extraFiles) - *extraFiles = append(*extraFiles, r) - return fd, w, nil - } -} - var ( ErrReceiveEnv = errors.New("environment variable not set") ) diff --git a/container/params_test.go b/container/params_test.go index 11d08850..4d2f84f6 100644 --- a/container/params_test.go +++ b/container/params_test.go @@ -1,10 +1,8 @@ package container_test import ( - "encoding/gob" "errors" "os" - "slices" "strconv" "syscall" "testing" @@ -52,74 +50,4 @@ func TestSetupReceive(t *testing.T) { t.Errorf("Receive: error = %v, want %v", err, syscall.EDOM) } }) - - t.Run("setup receive", func(t *testing.T) { - check := func(t *testing.T, useNilFdp bool) { - const key = "TEST_SETUP_RECEIVE" - payload := []uint64{syscall.MS_MGC_VAL, syscall.MS_MGC_MSK, syscall.MS_ASYNC, syscall.MS_ACTIVE} - - encoderDone := make(chan error, 1) - extraFiles := make([]*os.File, 0, 1) - deadline, _ := t.Deadline() - if fd, f, err := container.Setup(&extraFiles); err != nil { - t.Fatalf("Setup: error = %v", err) - } else if fd != 3 { - t.Fatalf("Setup: fd = %d, want 3", fd) - } else { - if err = f.SetDeadline(deadline); err != nil { - t.Fatal(err.Error()) - } - go func() { encoderDone <- gob.NewEncoder(f).Encode(payload) }() - } - - if len(extraFiles) != 1 { - t.Fatalf("extraFiles: len = %v, want 1", len(extraFiles)) - } - - var dupFd int - if fd, err := syscall.Dup(int(extraFiles[0].Fd())); err != nil { - t.Fatalf("Dup: error = %v", err) - } else { - syscall.CloseOnExec(fd) - dupFd = fd - t.Setenv(key, strconv.Itoa(fd)) - } - - var ( - gotPayload []uint64 - fdp *uintptr - ) - if !useNilFdp { - fdp = new(uintptr) - } - var closeFile func() error - if f, err := container.Receive(key, &gotPayload, fdp); err != nil { - t.Fatalf("Receive: error = %v", err) - } else { - closeFile = f - - if !slices.Equal(payload, gotPayload) { - t.Errorf("Receive: %#v, want %#v", gotPayload, payload) - } - } - if !useNilFdp { - if int(*fdp) != dupFd { - t.Errorf("Fd: %d, want %d", *fdp, dupFd) - } - } - - if err := <-encoderDone; err != nil { - t.Errorf("Encode: error = %v", err) - } - - if closeFile != nil { - if err := closeFile(); err != nil { - t.Errorf("Close: error = %v", err) - } - } - } - - t.Run("fp", func(t *testing.T) { check(t, false) }) - t.Run("nil", func(t *testing.T) { check(t, true) }) - }) } diff --git a/internal/outcome/process.go b/internal/outcome/process.go index 9b8fc537..44a62463 100644 --- a/internal/outcome/process.go +++ b/internal/outcome/process.go @@ -13,7 +13,6 @@ import ( "time" "hakurei.app/check" - "hakurei.app/container" "hakurei.app/fhs" "hakurei.app/hst" "hakurei.app/internal/info" @@ -372,17 +371,18 @@ func (k *outcome) start(ctx context.Context, msg message.Msg, // shim runs in the same session as monitor; see shim.go for behaviour cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGCONT) } - var shimPipe *os.File - if fd, w, err := container.Setup(&cmd.ExtraFiles); err != nil { + var shimPipe [2]*os.File + if r, w, err := os.Pipe(); err != nil { return cmd, nil, &hst.AppError{Step: "create shim setup pipe", Err: err} } else { - shimPipe = w cmd.Env = []string{ // passed through to shim by hsu - shimEnv + "=" + strconv.Itoa(fd), + shimEnv + "=" + strconv.Itoa(3+len(cmd.ExtraFiles)), // interpreted by hsu "HAKUREI_IDENTITY=" + k.state.identity.String(), } + cmd.ExtraFiles = append(cmd.ExtraFiles, r) + shimPipe[0], shimPipe[1] = r, w } if len(k.supp) > 0 { @@ -393,12 +393,16 @@ func (k *outcome) start(ctx context.Context, msg message.Msg, msg.Verbosef("setuid helper at %s", hsuPath) if err := cmd.Start(); err != nil { + _, _ = shimPipe[0].Close(), shimPipe[1].Close() msg.Resume() - return cmd, shimPipe, &hst.AppError{Step: "start setuid wrapper", Err: err} + return cmd, nil, &hst.AppError{Step: "start setuid wrapper", Err: err} + } + if err := shimPipe[0].Close(); err != nil { + msg.Verbose(err) } *startTime = time.Now().UTC() - return cmd, shimPipe, nil + return cmd, shimPipe[1], nil } // serveShim serves outcomeState through the shim setup pipe. @@ -411,11 +415,11 @@ func serveShim(msg message.Msg, shimPipe *os.File, state *outcomeState) error { msg.Verbose(err.Error()) } if err := gob.NewEncoder(shimPipe).Encode(state); err != nil { + _ = shimPipe.Close() msg.Resume() return &hst.AppError{Step: "transmit shim config", Err: err} } - _ = shimPipe.Close() - return nil + return shimPipe.Close() } // printMessageError prints the error message according to [message.GetMessage],