Skip to content

Commit

Permalink
fix an error caused by fd reuse race when starting runc init
Browse files Browse the repository at this point in the history
In #3987(0e9a335), we may use a memfd to copy run to start runc init,
due to a Go stdlib bug, we need to add safeExe to the set of
ExtraFiles otherwise it is possible for the stdlib to clobber the fd
during forkAndExecInChild1 and replace it with some other file that
might be malicious. This is less than ideal (because the descriptor
will be non-O_CLOEXEC) however we have protections in "runc init" to
stop us from leaking extra file descriptors.
See <golang/go#61751>.

There is a race situation when we are opening this memfd, if the fd 6
or 7 was closed at that time, maybe it will be reused by memfd.

Because we want to add safeExe to the set of ExtraFiles, if the fd of
safeExe is too small, go stdlib will dup3 it to another fd, or dup3 a
other fd to this fd, then it will cause the fd type cmd.Path refers to
a random path. (issue: #4294)

Signed-off-by: lfbzhm <[email protected]>
  • Loading branch information
lifubang committed Oct 18, 2024
1 parent d82235c commit e502359
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 0 deletions.
27 changes: 27 additions & 0 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,33 @@ func (c *Container) newParentProcess(p *Process) (parentProcess, error) {
}

if safeExe != nil {
// Because we want to add safeExe to the set of ExtraFiles, if the fd of safeExe is
// too small, go stdlib will dup3 it to another fd, or dup3 a other fd to this fd,
// then it will cause the fd type cmd.Path refers to a random path. (#4294)

// +1 means safeExe.
minFd := stdioFdCount + len(cmd.ExtraFiles) + 1
if p.Init {
// This refers to fifo.
minFd++
}
if int(safeExe.Fd()) <= minFd {
maxFd, err := utils.GetMaxFds()
if err != nil {
return nil, fmt.Errorf("unable to get the max opened fd of current process: %w", err)
}
maxFd++
if err := unix.Dup3(int(safeExe.Fd()), maxFd, unix.O_CLOEXEC); err != nil {
return nil, fmt.Errorf("unable to dup3 the fd from %d to %d: %w", safeExe.Fd(), maxFd, err)
}
cmd.Path = "/proc/self/fd/" + strconv.Itoa(maxFd)
newSafeExe := os.NewFile(uintptr(maxFd), safeExe.Name())
if err := safeExe.Close(); err != nil {
return nil, fmt.Errorf("unable to close old safe exe(%d): %w", safeExe.Fd(), err)
}
safeExe = newSafeExe
}

// Due to a Go stdlib bug, we need to add safeExe to the set of
// ExtraFiles otherwise it is possible for the stdlib to clobber the fd
// during forkAndExecInChild1 and replace it with some other file that
Expand Down
11 changes: 11 additions & 0 deletions libcontainer/utils/utils_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ func fdRangeFrom(minFd int, fn fdFunc) error {
return nil
}

// GetMaxFds returns the max opened fd of current process.
func GetMaxFds() (int, error) {
maxFd := -1
err := fdRangeFrom(-1, func(fd int) {
if fd > maxFd {
maxFd = fd
}
})
return maxFd, err
}

// CloseExecFrom sets the O_CLOEXEC flag on all file descriptors greater or
// equal to minFd in the current process.
func CloseExecFrom(minFd int) error {
Expand Down

0 comments on commit e502359

Please sign in to comment.