diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 70fe4329c4..61bbd1ff15 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -533,8 +533,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } // Create files and add data to the volume mountpoint based on the Items in the volume for k, v := range v.Items { - dataPath := filepath.Join(mountPoint, k) - f, err := os.Create(dataPath) + f, err := openPathSafely(mountPoint, k) if err != nil { return nil, nil, fmt.Errorf("cannot create file %q at volume mountpoint %q: %w", k, mountPoint, err) } diff --git a/pkg/domain/infra/abi/play_linux.go b/pkg/domain/infra/abi/play_linux.go new file mode 100644 index 0000000000..a0f9811516 --- /dev/null +++ b/pkg/domain/infra/abi/play_linux.go @@ -0,0 +1,18 @@ +//go:build !remote + +package abi + +import ( + "os" + + securejoin "github.com/cyphar/filepath-securejoin" +) + +// openSymlinkPath opens the path under root using securejoin.OpenatInRoot(). +func openSymlinkPath(root *os.File, unsafePath string, flags int) (*os.File, error) { + file, err := securejoin.OpenatInRoot(root, unsafePath) + if err != nil { + return nil, err + } + return securejoin.Reopen(file, flags) +} diff --git a/pkg/domain/infra/abi/play_unsupported.go b/pkg/domain/infra/abi/play_unsupported.go new file mode 100644 index 0000000000..3ecbae7cc1 --- /dev/null +++ b/pkg/domain/infra/abi/play_unsupported.go @@ -0,0 +1,13 @@ +//go:build !linux && !remote + +package abi + +import ( + "errors" + "os" +) + +// openSymlinkPath is not supported on this platform. +func openSymlinkPath(root *os.File, unsafePath string, flags int) (*os.File, error) { + return nil, errors.New("cannot safely open symlink on this platform") +} diff --git a/pkg/domain/infra/abi/play_utils.go b/pkg/domain/infra/abi/play_utils.go index 482a158e6e..0acfc0ed09 100644 --- a/pkg/domain/infra/abi/play_utils.go +++ b/pkg/domain/infra/abi/play_utils.go @@ -1,6 +1,13 @@ package abi -import "github.com/containers/podman/v4/libpod/define" +import ( + "fmt" + "os" + "strings" + + "github.com/containers/podman/v4/libpod/define" + "golang.org/x/sys/unix" +) // getSdNotifyMode returns the `sdNotifyAnnotation/$name` for the specified // name. If name is empty, it'll only look for `sdNotifyAnnotation`. @@ -14,3 +21,33 @@ func getSdNotifyMode(annotations map[string]string, name string) (string, error) } return mode, define.ValidateSdNotifyMode(mode) } + +// openPathSafely opens the given name under the trusted root path, the unsafeName +// must be a single path component and not contain "/". +// The resulting path will be opened or created if it does not exists. +// Following of symlink is done within staying under root, escapes outsides +// of root are not allowed and prevent. +// +// This custom function is needed because securejoin.SecureJoin() is not race safe +// and the volume might be mounted in another container that could swap in a symlink +// after the function ahs run. securejoin.OpenInRoot() doesn't work either because +// it cannot create files and doesn't work on freebsd. +func openPathSafely(root, unsafeName string) (*os.File, error) { + if strings.Contains(unsafeName, "/") { + return nil, fmt.Errorf("name %q must not contain path separator", unsafeName) + } + fdDir, err := os.OpenFile(root, unix.O_RDONLY, 0) + if err != nil { + return nil, err + } + defer fdDir.Close() + flags := unix.O_CREAT | unix.O_WRONLY | unix.O_TRUNC | unix.O_CLOEXEC + fd, err := unix.Openat(int(fdDir.Fd()), unsafeName, flags|unix.O_NOFOLLOW, 0o644) + if err == nil { + return os.NewFile(uintptr(fd), unsafeName), nil + } + if err == unix.ELOOP { + return openSymlinkPath(fdDir, unsafeName, flags) + } + return nil, &os.PathError{Op: "openat", Path: unsafeName, Err: err} +}