diff options
| author | Divya Chellam <divya.chellam@windriver.com> | 2025-10-13 17:17:25 +0530 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2025-10-13 22:01:08 -0400 |
| commit | 38008d99d5bedc7d9769b9e95e3d6019a2df1698 (patch) | |
| tree | 4108fde201b18d05d8adcb4cfb6ee475637b1d22 | |
| parent | 898239e810acbb7db93299f20deec8afe434f11b (diff) | |
| download | meta-virtualization-walnascar.tar.gz | |
podman: fix CVE-2025-9566walnascar
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 <divya.chellam@windriver.com>
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
| -rw-r--r-- | recipes-containers/podman/podman/CVE-2025-9566.patch | 152 | ||||
| -rw-r--r-- | recipes-containers/podman/podman_git.bb | 1 |
2 files changed, 153 insertions, 0 deletions
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 @@ | |||
| 1 | From ca994186f07822b9048fe711b6903e51614d3e15 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Paul Holzinger <pholzing@redhat.com> | ||
| 3 | Date: Fri, 29 Aug 2025 15:39:38 +0200 | ||
| 4 | Subject: [PATCH] kube play: don't follow volume symlinks onto the host | ||
| 5 | |||
| 6 | For ConfigMap and Secret kube play volumes podman populates the data | ||
| 7 | from the yaml. However the volume content is not controlled by us and we | ||
| 8 | can be tricked following a symlink to a file on the host instead. | ||
| 9 | |||
| 10 | Fixes: CVE-2025-9566 | ||
| 11 | |||
| 12 | Signed-off-by: Paul Holzinger <pholzing@redhat.com> | ||
| 13 | (cherry picked from commit 43fbde4e665fe6cee6921868f04b7ccd3de5ad89) | ||
| 14 | Signed-off-by: Paul Holzinger <pholzing@redhat.com> | ||
| 15 | |||
| 16 | CVE: CVE-2025-9566 | ||
| 17 | |||
| 18 | Upstream-Status: Backport [https://github.com/containers/podman/commit/ca994186f07822b9048fe711b6903e51614d3e15] | ||
| 19 | |||
| 20 | Signed-off-by: Divya Chellam <divya.chellam@windriver.com> | ||
| 21 | --- | ||
| 22 | pkg/domain/infra/abi/play.go | 5 ++- | ||
| 23 | pkg/domain/infra/abi/play_linux.go | 18 +++++++++++ | ||
| 24 | pkg/domain/infra/abi/play_unsupported.go | 13 ++++++++ | ||
| 25 | pkg/domain/infra/abi/play_utils.go | 39 +++++++++++++++++++++++- | ||
| 26 | 4 files changed, 71 insertions(+), 4 deletions(-) | ||
| 27 | create mode 100644 pkg/domain/infra/abi/play_linux.go | ||
| 28 | create mode 100644 pkg/domain/infra/abi/play_unsupported.go | ||
| 29 | |||
| 30 | diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go | ||
| 31 | index 6ffbf4cf54..2fa2752d7c 100644 | ||
| 32 | --- a/pkg/domain/infra/abi/play.go | ||
| 33 | +++ b/pkg/domain/infra/abi/play.go | ||
| 34 | @@ -808,8 +808,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY | ||
| 35 | defaultMode := v.DefaultMode | ||
| 36 | // Create files and add data to the volume mountpoint based on the Items in the volume | ||
| 37 | for k, v := range v.Items { | ||
| 38 | - dataPath := filepath.Join(mountPoint, k) | ||
| 39 | - f, err := os.Create(dataPath) | ||
| 40 | + f, err := openPathSafely(mountPoint, k) | ||
| 41 | if err != nil { | ||
| 42 | return nil, nil, fmt.Errorf("cannot create file %q at volume mountpoint %q: %w", k, mountPoint, err) | ||
| 43 | } | ||
| 44 | @@ -819,7 +818,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY | ||
| 45 | return nil, nil, err | ||
| 46 | } | ||
| 47 | // Set file permissions | ||
| 48 | - if err := os.Chmod(f.Name(), os.FileMode(defaultMode)); err != nil { | ||
| 49 | + if err := f.Chmod(os.FileMode(defaultMode)); err != nil { | ||
| 50 | return nil, nil, err | ||
| 51 | } | ||
| 52 | } | ||
| 53 | diff --git a/pkg/domain/infra/abi/play_linux.go b/pkg/domain/infra/abi/play_linux.go | ||
| 54 | new file mode 100644 | ||
| 55 | index 0000000000..a0f9811516 | ||
| 56 | --- /dev/null | ||
| 57 | +++ b/pkg/domain/infra/abi/play_linux.go | ||
| 58 | @@ -0,0 +1,18 @@ | ||
| 59 | +//go:build !remote | ||
| 60 | + | ||
| 61 | +package abi | ||
| 62 | + | ||
| 63 | +import ( | ||
| 64 | + "os" | ||
| 65 | + | ||
| 66 | + securejoin "github.com/cyphar/filepath-securejoin" | ||
| 67 | +) | ||
| 68 | + | ||
| 69 | +// openSymlinkPath opens the path under root using securejoin.OpenatInRoot(). | ||
| 70 | +func openSymlinkPath(root *os.File, unsafePath string, flags int) (*os.File, error) { | ||
| 71 | + file, err := securejoin.OpenatInRoot(root, unsafePath) | ||
| 72 | + if err != nil { | ||
| 73 | + return nil, err | ||
| 74 | + } | ||
| 75 | + return securejoin.Reopen(file, flags) | ||
| 76 | +} | ||
| 77 | diff --git a/pkg/domain/infra/abi/play_unsupported.go b/pkg/domain/infra/abi/play_unsupported.go | ||
| 78 | new file mode 100644 | ||
| 79 | index 0000000000..3ecbae7cc1 | ||
| 80 | --- /dev/null | ||
| 81 | +++ b/pkg/domain/infra/abi/play_unsupported.go | ||
| 82 | @@ -0,0 +1,13 @@ | ||
| 83 | +//go:build !linux && !remote | ||
| 84 | + | ||
| 85 | +package abi | ||
| 86 | + | ||
| 87 | +import ( | ||
| 88 | + "errors" | ||
| 89 | + "os" | ||
| 90 | +) | ||
| 91 | + | ||
| 92 | +// openSymlinkPath is not supported on this platform. | ||
| 93 | +func openSymlinkPath(root *os.File, unsafePath string, flags int) (*os.File, error) { | ||
| 94 | + return nil, errors.New("cannot safely open symlink on this platform") | ||
| 95 | +} | ||
| 96 | diff --git a/pkg/domain/infra/abi/play_utils.go b/pkg/domain/infra/abi/play_utils.go | ||
| 97 | index 7285d9c9b9..217b656997 100644 | ||
| 98 | --- a/pkg/domain/infra/abi/play_utils.go | ||
| 99 | +++ b/pkg/domain/infra/abi/play_utils.go | ||
| 100 | @@ -2,7 +2,14 @@ | ||
| 101 | |||
| 102 | package abi | ||
| 103 | |||
| 104 | -import "github.com/containers/podman/v5/libpod/define" | ||
| 105 | +import ( | ||
| 106 | + "fmt" | ||
| 107 | + "os" | ||
| 108 | + "strings" | ||
| 109 | + | ||
| 110 | + "github.com/containers/podman/v5/libpod/define" | ||
| 111 | + "golang.org/x/sys/unix" | ||
| 112 | +) | ||
| 113 | |||
| 114 | // getSdNotifyMode returns the `sdNotifyAnnotation/$name` for the specified | ||
| 115 | // name. If name is empty, it'll only look for `sdNotifyAnnotation`. | ||
| 116 | @@ -16,3 +23,33 @@ func getSdNotifyMode(annotations map[string]string, name string) (string, error) | ||
| 117 | } | ||
| 118 | return mode, define.ValidateSdNotifyMode(mode) | ||
| 119 | } | ||
| 120 | + | ||
| 121 | +// openPathSafely opens the given name under the trusted root path, the unsafeName | ||
| 122 | +// must be a single path component and not contain "/". | ||
| 123 | +// The resulting path will be opened or created if it does not exists. | ||
| 124 | +// Following of symlink is done within staying under root, escapes outsides | ||
| 125 | +// of root are not allowed and prevent. | ||
| 126 | +// | ||
| 127 | +// This custom function is needed because securejoin.SecureJoin() is not race safe | ||
| 128 | +// and the volume might be mounted in another container that could swap in a symlink | ||
| 129 | +// after the function ahs run. securejoin.OpenInRoot() doesn't work either because | ||
| 130 | +// it cannot create files and doesn't work on freebsd. | ||
| 131 | +func openPathSafely(root, unsafeName string) (*os.File, error) { | ||
| 132 | + if strings.Contains(unsafeName, "/") { | ||
| 133 | + return nil, fmt.Errorf("name %q must not contain path separator", unsafeName) | ||
| 134 | + } | ||
| 135 | + fdDir, err := os.OpenFile(root, unix.O_RDONLY, 0) | ||
| 136 | + if err != nil { | ||
| 137 | + return nil, err | ||
| 138 | + } | ||
| 139 | + defer fdDir.Close() | ||
| 140 | + flags := unix.O_CREAT | unix.O_WRONLY | unix.O_TRUNC | unix.O_CLOEXEC | ||
| 141 | + fd, err := unix.Openat(int(fdDir.Fd()), unsafeName, flags|unix.O_NOFOLLOW, 0o644) | ||
| 142 | + if err == nil { | ||
| 143 | + return os.NewFile(uintptr(fd), unsafeName), nil | ||
| 144 | + } | ||
| 145 | + if err == unix.ELOOP { | ||
| 146 | + return openSymlinkPath(fdDir, unsafeName, flags) | ||
| 147 | + } | ||
| 148 | + return nil, &os.PathError{Op: "openat", Path: unsafeName, Err: err} | ||
| 149 | +} | ||
| 150 | -- | ||
| 151 | 2.40.0 | ||
| 152 | |||
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 = " \ | |||
| 22 | ${@bb.utils.contains('PACKAGECONFIG', 'rootless', 'file://50-podman-rootless.conf', '', d)} \ | 22 | ${@bb.utils.contains('PACKAGECONFIG', 'rootless', 'file://50-podman-rootless.conf', '', d)} \ |
| 23 | file://run-ptest \ | 23 | file://run-ptest \ |
| 24 | file://CVE-2025-6032.patch;patchdir=src/import \ | 24 | file://CVE-2025-6032.patch;patchdir=src/import \ |
| 25 | file://CVE-2025-9566.patch;patchdir=src/import \ | ||
| 25 | " | 26 | " |
| 26 | 27 | ||
| 27 | LICENSE = "Apache-2.0" | 28 | LICENSE = "Apache-2.0" |
