diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-11-12 00:18:11 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-11-12 00:18:29 +0900 |
| commit | ac3463589035394bedbedb15a323298251ed2c14 (patch) | |
| tree | d8b863e0faf4d44c2d4e12a8378f7fe9820c04d1 /container/container.go | |
| parent | 9dec9dbc4bbd09a59cf35f4000d26794b42edcfc (diff) | |
container: set FD_CLOEXEC on all open files
While fd created from this side always has the FD_CLOEXEC flag, the same is not true for files left open by the parent. This change prevents those files from leaking into the container.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container/container.go')
| -rw-r--r-- | container/container.go | 45 |
1 files changed, 45 insertions, 0 deletions
diff --git a/container/container.go b/container/container.go index f9e7cf12..428309c0 100644 --- a/container/container.go +++ b/container/container.go @@ -11,6 +11,7 @@ import ( "os/exec" "runtime" "strconv" + "sync" . "syscall" "time" @@ -143,11 +144,18 @@ func (e *StartError) Error() string { // Message returns a user-facing error message. func (e *StartError) Message() string { if e.Passthrough { + var ( + numError *strconv.NumError + ) + switch { case errors.As(e.Err, new(*os.PathError)), errors.As(e.Err, new(*os.SyscallError)): return "cannot " + e.Err.Error() + case errors.As(e.Err, &numError) && numError != nil: + return "cannot parse " + strconv.Quote(numError.Num) + ": " + numError.Err.Error() + default: return e.Err.Error() } @@ -158,6 +166,39 @@ func (e *StartError) Message() string { return "cannot " + e.Error() } +// for ensureCloseOnExec +var ( + closeOnExecOnce sync.Once + closeOnExecErr error +) + +// ensureCloseOnExec ensures all currently open file descriptors have the syscall.FD_CLOEXEC flag set. +// This is only ran once as it is intended to handle files left open by the parent, and any file opened +// on this side should already have syscall.FD_CLOEXEC set. +func ensureCloseOnExec() error { + closeOnExecOnce.Do(func() { + const fdPrefixPath = "/proc/self/fd/" + + var entries []os.DirEntry + if entries, closeOnExecErr = os.ReadDir(fdPrefixPath); closeOnExecErr != nil { + return + } + + var fd int + for _, ent := range entries { + if fd, closeOnExecErr = strconv.Atoi(ent.Name()); closeOnExecErr != nil { + break // not reached + } + CloseOnExec(fd) + } + }) + + if closeOnExecErr == nil { + return nil + } + return &StartError{Fatal: true, Step: "set FD_CLOEXEC on all open files", Err: closeOnExecErr, Passthrough: true} +} + // Start starts the container init. The init process blocks until Serve is called. func (p *Container) Start() error { if p == nil || p.cmd == nil || @@ -168,6 +209,10 @@ func (p *Container) Start() error { return errors.New("container: already started") } + if err := ensureCloseOnExec(); err != nil { + return err + } + // map to overflow id to work around ownership checks if p.Uid < 1 { p.Uid = OverflowUid(p.msg) |
