Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 10 additions & 64 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"golang.org/x/sys/unix"

"github.com/opencontainers/cgroups"
"github.com/opencontainers/runc/internal/linux"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/exeseal"
"github.com/opencontainers/runc/libcontainer/intelrdt"
Expand Down Expand Up @@ -231,74 +232,19 @@ func (c *Container) Exec() error {
}

func (c *Container) exec() error {
path := filepath.Join(c.stateDir, execFifoFilename)
pid := c.initProcess.pid()
blockingFifoOpenCh := awaitFifoOpen(path)
for {
select {
case result := <-blockingFifoOpenCh:
return handleFifoResult(result)

case <-time.After(time.Millisecond * 100):
stat, err := system.Stat(pid)
if err != nil || stat.State == system.Zombie {
// could be because process started, ran, and completed between our 100ms timeout and our system.Stat() check.
// see if the fifo exists and has data (with a non-blocking open, which will succeed if the writing process is complete).
if err := handleFifoResult(fifoOpen(path, false)); err != nil {
return errors.New("container process is already dead")
}
return nil
}
}
}
}

func readFromExecFifo(execFifo io.Reader) error {
data, err := io.ReadAll(execFifo)
fifoPath := filepath.Join(c.stateDir, execFifoFilename)
fd, err := linux.Open(fifoPath, unix.O_WRONLY|unix.O_CLOEXEC, 0)
if err != nil {
return err
}
if len(data) <= 0 {
return errors.New("cannot start an already running container")
defer unix.Close(fd)
if _, err := unix.Write(fd, []byte("0")); err != nil {
return &os.PathError{Op: "write exec fifo", Path: fifoPath, Err: err}
}
return nil
}

func awaitFifoOpen(path string) <-chan openResult {
fifoOpened := make(chan openResult)
go func() {
result := fifoOpen(path, true)
fifoOpened <- result
}()
return fifoOpened
}

func fifoOpen(path string, block bool) openResult {
flags := os.O_RDONLY
if !block {
flags |= unix.O_NONBLOCK
}
f, err := os.OpenFile(path, flags, 0)
if err != nil {
return openResult{err: fmt.Errorf("exec fifo: %w", err)}
}
return openResult{file: f}
}

func handleFifoResult(result openResult) error {
if result.err != nil {
return result.err
}
f := result.file
defer f.Close()
if err := readFromExecFifo(f); err != nil {
if err := os.Remove(fifoPath); os.IsNotExist(err) {
return err
}
err := os.Remove(f.Name())
if err == nil || os.IsNotExist(err) {
return nil
}
return err
return nil
}

type openResult struct {
Expand Down Expand Up @@ -450,7 +396,7 @@ func (c *Container) createExecFifo() (retErr error) {
}

fifoName := filepath.Join(c.stateDir, execFifoFilename)
if err := unix.Mkfifo(fifoName, 0o622); err != nil {
if err := unix.Mkfifo(fifoName, 0o644); err != nil {
return &os.PathError{Op: "mkfifo", Path: fifoName, Err: err}
}
defer func() {
Expand All @@ -459,7 +405,7 @@ func (c *Container) createExecFifo() (retErr error) {
}
}()
// Ensure permission bits (can be different because of umask).
if err := os.Chmod(fifoName, 0o622); err != nil {
if err := os.Chmod(fifoName, 0o644); err != nil {
return err
}
return os.Chown(fifoName, rootuid, rootgid)
Expand Down
35 changes: 30 additions & 5 deletions libcontainer/standard_init_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package libcontainer
import (
"errors"
"fmt"
"io"
"os"
"os/exec"

Expand Down Expand Up @@ -266,14 +267,15 @@ func (l *linuxStandardInit) Init() error {
// user process. We open it through /proc/self/fd/$fd, because the fd that
// was given to us was an O_PATH fd to the fifo itself. Linux allows us to
// re-open an O_PATH fd through /proc.
fd, err := linux.Open(fifoPath, unix.O_WRONLY|unix.O_CLOEXEC, 0)
if err != nil {

result := fifoOpen(fifoPath, true)
if result.err != nil {
return err
}
if _, err := unix.Write(fd, []byte("0")); err != nil {
return &os.PathError{Op: "write exec fifo", Path: fifoPath, Err: err}
if err := readFromExecFifo(result.file); err != nil {
return err
}

result.file.Close()
// Close the O_PATH fifofd fd before exec because the kernel resets
// dumpable in the wrong order. This has been fixed in newer kernels, but
// we keep this to ensure CVE-2016-9962 doesn't re-emerge on older kernels.
Expand Down Expand Up @@ -305,3 +307,26 @@ func (l *linuxStandardInit) Init() error {
}
return linux.Exec(name, l.config.Args, l.config.Env)
}

func fifoOpen(path string, block bool) openResult {
flags := os.O_RDONLY
if !block {
flags |= unix.O_NONBLOCK
}
f, err := os.OpenFile(path, flags, 0)
if err != nil {
return openResult{err: fmt.Errorf("exec fifo: %w", err)}
}
return openResult{file: f}
}

func readFromExecFifo(execFifo io.Reader) error {
data, err := io.ReadAll(execFifo)
if err != nil {
return err
}
if len(data) <= 0 {
return errors.New("cannot start an already running container")
}
return nil
}
Loading