From 38008d99d5bedc7d9769b9e95e3d6019a2df1698 Mon Sep 17 00:00:00 2001 From: Divya Chellam Date: Mon, 13 Oct 2025 17:17:25 +0530 Subject: podman: fix CVE-2025-9566 There's a vulnerability in podman where an attacker may use the kube play command to overwrite host files when the kube file container a Secrete or a ConfigMap volume mount and such volume contains a symbolic link to a host file path. In a successful attack, the attacker can only control the target file to be overwritten but not the content to be written into the file. [EOL][EOL]Binary-Affected: podman[EOL]Upstream-version-introduced: v4.0.0[EOL]Upstream-version-fixed: v5.6.1 Reference: https://security-tracker.debian.org/tracker/CVE-2025-9566 Upstream-patch: https://github.com/containers/podman/commit/ca994186f07822b9048fe711b6903e51614d3e15 Signed-off-by: Divya Chellam Signed-off-by: Bruce Ashfield --- .../podman/podman/CVE-2025-9566.patch | 152 +++++++++++++++++++++ recipes-containers/podman/podman_git.bb | 1 + 2 files changed, 153 insertions(+) create mode 100644 recipes-containers/podman/podman/CVE-2025-9566.patch (limited to 'recipes-containers') diff --git a/recipes-containers/podman/podman/CVE-2025-9566.patch b/recipes-containers/podman/podman/CVE-2025-9566.patch new file mode 100644 index 00000000..7e5cbe8b --- /dev/null +++ b/recipes-containers/podman/podman/CVE-2025-9566.patch @@ -0,0 +1,152 @@ +From ca994186f07822b9048fe711b6903e51614d3e15 Mon Sep 17 00:00:00 2001 +From: Paul Holzinger +Date: Fri, 29 Aug 2025 15:39:38 +0200 +Subject: [PATCH] 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 +(cherry picked from commit 43fbde4e665fe6cee6921868f04b7ccd3de5ad89) +Signed-off-by: Paul Holzinger + +CVE: CVE-2025-9566 + +Upstream-Status: Backport [https://github.com/containers/podman/commit/ca994186f07822b9048fe711b6903e51614d3e15] + +Signed-off-by: Divya Chellam +--- + pkg/domain/infra/abi/play.go | 5 ++- + pkg/domain/infra/abi/play_linux.go | 18 +++++++++++ + pkg/domain/infra/abi/play_unsupported.go | 13 ++++++++ + pkg/domain/infra/abi/play_utils.go | 39 +++++++++++++++++++++++- + 4 files changed, 71 insertions(+), 4 deletions(-) + create mode 100644 pkg/domain/infra/abi/play_linux.go + create mode 100644 pkg/domain/infra/abi/play_unsupported.go + +diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go +index 6ffbf4cf54..2fa2752d7c 100644 +--- a/pkg/domain/infra/abi/play.go ++++ b/pkg/domain/infra/abi/play.go +@@ -808,8 +808,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) + } +@@ -819,7 +818,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 + } + } +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 7285d9c9b9..217b656997 100644 +--- a/pkg/domain/infra/abi/play_utils.go ++++ b/pkg/domain/infra/abi/play_utils.go +@@ -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} ++} +-- +2.40.0 + diff --git a/recipes-containers/podman/podman_git.bb b/recipes-containers/podman/podman_git.bb index d98521ba..dbbe59c3 100644 --- a/recipes-containers/podman/podman_git.bb +++ b/recipes-containers/podman/podman_git.bb @@ -22,6 +22,7 @@ SRC_URI = " \ ${@bb.utils.contains('PACKAGECONFIG', 'rootless', 'file://50-podman-rootless.conf', '', d)} \ file://run-ptest \ file://CVE-2025-6032.patch;patchdir=src/import \ + file://CVE-2025-9566.patch;patchdir=src/import \ " LICENSE = "Apache-2.0" -- cgit v1.2.3-54-g00ecf