mirror of
https://github.com/containers/podman.git
synced 2025-09-06 22:37:37 +00:00
kube play: don't follow volume symlinks onto the host
For ConfigMap and Secret kube play volumes podman populates the data from the yaml. However the volume content is not controlled by us and we can be tricked following a symlink to a file on the host instead. Fixes: CVE-2025-9566 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
@@ -810,8 +810,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
defaultMode := v.DefaultMode
|
||||
// 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)
|
||||
}
|
||||
@@ -821,7 +820,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
return nil, nil, err
|
||||
}
|
||||
// Set file permissions
|
||||
if err := os.Chmod(f.Name(), os.FileMode(defaultMode)); err != nil {
|
||||
if err := f.Chmod(os.FileMode(defaultMode)); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
18
pkg/domain/infra/abi/play_linux.go
Normal file
18
pkg/domain/infra/abi/play_linux.go
Normal file
@@ -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)
|
||||
}
|
13
pkg/domain/infra/abi/play_unsupported.go
Normal file
13
pkg/domain/infra/abi/play_unsupported.go
Normal file
@@ -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")
|
||||
}
|
@@ -2,7 +2,14 @@
|
||||
|
||||
package abi
|
||||
|
||||
import "github.com/containers/podman/v5/libpod/define"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v5/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`.
|
||||
@@ -16,3 +23,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}
|
||||
}
|
||||
|
Reference in New Issue
Block a user