Skip to content

Commit ff15af1

Browse files
Merge pull request #27023 from TomSweeneyRedHat/dev/tsweeney/CVE-2025-9566-v4.9-rhel
[v4.9-rhel] CVE-2025-9566 Fixes
2 parents aa8114d + 55abecb commit ff15af1

25 files changed

+2198
-106
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ require (
2323
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09
2424
github.com/coreos/stream-metadata-go v0.4.4
2525
github.com/crc-org/vfkit v0.1.2-0.20231030102423-f3c783d34420
26-
github.com/cyphar/filepath-securejoin v0.2.4
26+
github.com/cyphar/filepath-securejoin v0.4.1
2727
github.com/digitalocean/go-qemu v0.0.0-20230711162256-2e3d0186973e
2828
github.com/docker/distribution v2.8.3+incompatible
2929
github.com/docker/docker v24.0.7+incompatible

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@ github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h
319319
github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
320320
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
321321
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
322-
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
323-
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
322+
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
323+
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
324324
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
325325
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
326326
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=

pkg/domain/infra/abi/play.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,8 +638,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
638638
defaultMode := v.DefaultMode
639639
// Create files and add data to the volume mountpoint based on the Items in the volume
640640
for k, v := range v.Items {
641-
dataPath := filepath.Join(mountPoint, k)
642-
f, err := os.Create(dataPath)
641+
f, err := openPathSafely(mountPoint, k)
643642
if err != nil {
644643
return nil, nil, fmt.Errorf("cannot create file %q at volume mountpoint %q: %w", k, mountPoint, err)
645644
}
@@ -649,7 +648,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
649648
return nil, nil, err
650649
}
651650
// Set file permissions
652-
if err := os.Chmod(f.Name(), os.FileMode(defaultMode)); err != nil {
651+
if err := f.Chmod(os.FileMode(defaultMode)); err != nil {
653652
return nil, nil, err
654653
}
655654
}

pkg/domain/infra/abi/play_linux.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//go:build !remote
2+
3+
package abi
4+
5+
import (
6+
"os"
7+
8+
securejoin "github.com/cyphar/filepath-securejoin"
9+
)
10+
11+
// openSymlinkPath opens the path under root using securejoin.OpenatInRoot().
12+
func openSymlinkPath(root *os.File, unsafePath string, flags int) (*os.File, error) {
13+
file, err := securejoin.OpenatInRoot(root, unsafePath)
14+
if err != nil {
15+
return nil, err
16+
}
17+
return securejoin.Reopen(file, flags)
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !linux && !remote
2+
3+
package abi
4+
5+
import (
6+
"errors"
7+
"os"
8+
)
9+
10+
// openSymlinkPath is not supported on this platform.
11+
func openSymlinkPath(root *os.File, unsafePath string, flags int) (*os.File, error) {
12+
return nil, errors.New("cannot safely open symlink on this platform")
13+
}

pkg/domain/infra/abi/play_utils.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package abi
22

3-
import "github.com/containers/podman/v4/libpod/define"
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"github.com/containers/podman/v4/libpod/define"
9+
"golang.org/x/sys/unix"
10+
)
411

512
// getSdNotifyMode returns the `sdNotifyAnnotation/$name` for the specified
613
// 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)
1421
}
1522
return mode, define.ValidateSdNotifyMode(mode)
1623
}
24+
25+
// openPathSafely opens the given name under the trusted root path, the unsafeName
26+
// must be a single path component and not contain "/".
27+
// The resulting path will be opened or created if it does not exists.
28+
// Following of symlink is done within staying under root, escapes outsides
29+
// of root are not allowed and prevent.
30+
//
31+
// This custom function is needed because securejoin.SecureJoin() is not race safe
32+
// and the volume might be mounted in another container that could swap in a symlink
33+
// after the function ahs run. securejoin.OpenInRoot() doesn't work either because
34+
// it cannot create files and doesn't work on freebsd.
35+
func openPathSafely(root, unsafeName string) (*os.File, error) {
36+
if strings.Contains(unsafeName, "/") {
37+
return nil, fmt.Errorf("name %q must not contain path separator", unsafeName)
38+
}
39+
fdDir, err := os.OpenFile(root, unix.O_RDONLY, 0)
40+
if err != nil {
41+
return nil, err
42+
}
43+
defer fdDir.Close()
44+
flags := unix.O_CREAT | unix.O_WRONLY | unix.O_TRUNC | unix.O_CLOEXEC
45+
fd, err := unix.Openat(int(fdDir.Fd()), unsafeName, flags|unix.O_NOFOLLOW, 0o644)
46+
if err == nil {
47+
return os.NewFile(uintptr(fd), unsafeName), nil
48+
}
49+
if err == unix.ELOOP {
50+
return openSymlinkPath(fdDir, unsafeName, flags)
51+
}
52+
return nil, &os.PathError{Op: "openat", Path: unsafeName, Err: err}
53+
}

test/e2e/play_kube_test.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1913,7 +1913,7 @@ func getPersistentVolumeClaimVolume(vName string) *Volume {
19131913

19141914
// getConfigMap returns a new ConfigMap Volume given the name and items
19151915
// of the ConfigMap.
1916-
func getConfigMapVolume(vName string, items []map[string]string, optional bool, defaultMode *int32) *Volume { //nolint:unparam
1916+
func getConfigMapVolume(vName string, items []map[string]string, optional bool, defaultMode *int32) *Volume {
19171917
vol := &Volume{
19181918
VolumeType: "ConfigMap",
19191919
Name: defaultVolName,
@@ -6337,4 +6337,32 @@ spec:
63376337
Expect(execArr[len(execArr)-1]).To(Not(ContainSubstring(arr[len(arr)-1])))
63386338
})
63396339

6340+
It("CVE-2025-9566 regression test - ConfigMap mount", func() {
6341+
testfile := filepath.Join(podmanTest.TempDir, "testfile")
6342+
volumeName := "cm-vol"
6343+
cm := getConfigMap(withConfigMapName(volumeName), withConfigMapData("foo", "content1"))
6344+
cmYaml, err := getKubeYaml("configmap", cm)
6345+
Expect(err).ToNot(HaveOccurred())
6346+
6347+
ctrName := "ctr1"
6348+
podName := "pod1"
6349+
// create a symlink at the volume mount location so we can make sure we don't resolve that to the host location.
6350+
ctr := getCtr(withName(ctrName), withVolumeMount("/test", "", false), withImage(CITEST_IMAGE), withCmd([]string{"sh", "-c", "ln -sf " + testfile + " /test/foo"}))
6351+
pod := getPod(withPodName(podName), withVolume(getConfigMapVolume(volumeName, nil, false, nil)), withCtr(ctr))
6352+
podYaml, err := getKubeYaml("pod", pod)
6353+
Expect(err).ToNot(HaveOccurred())
6354+
yamls := []string{cmYaml, podYaml}
6355+
err = generateMultiDocKubeYaml(yamls, kubeYaml)
6356+
Expect(err).ToNot(HaveOccurred())
6357+
6358+
podmanTest.PodmanExitCleanly("kube", "play", kubeYaml)
6359+
// wait for the container to finish to ensure the symlink was created
6360+
podmanTest.PodmanExitCleanly("wait", podName+"-"+ctrName)
6361+
podmanTest.PodmanExitCleanly("kube", "down", kubeYaml)
6362+
kube := podmanTest.Podman([]string{"kube", "play", kubeYaml})
6363+
kube.WaitWithDefaultTimeout()
6364+
Expect(kube).To(ExitWithError(125, `cannot create file "foo" at volume mountpoint`))
6365+
6366+
Expect(testfile).ToNot(BeAnExistingFile(), "file should never be created on the host")
6367+
})
63406368
})

0 commit comments

Comments
 (0)