* [meta-virtualization][scarthgap][PATCH] podman: Fix CVE-2026-55686
@ 2026-07-07 7:25 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-20 21:37 ` Bruce Ashfield
0 siblings, 1 reply; 2+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-07-07 7:25 UTC (permalink / raw)
To: meta-virtualization
From: Deepak Rathore <deeratho@cisco.com>
- This patch applies the upstream v5.7.1 backport for CVE-2026-55686.
- The upstream fix commit is referenced in [1],the public CVE advisory
is referenced in [2], and the dependent upstream workdir test commit
is referenced in [3].
- The dependent patch is carried because the CVE fix extends that
upstream symlinked-workdir regression test.
[1] https://github.com/containers/podman/commit/7ce2e00ab140c11a68301f0b161f51984131a858
[2] https://github.com/advisories/GHSA-q6r4-3wmg-fwcq
[3] https://github.com/podman-container-tools/podman/commit/e576e002e9d95ae6b37265d9e0b3deaa4c43d73f
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
.../podman/CVE-2026-55686-dependent.patch | 53 +++++
.../podman/podman/CVE-2026-55686.patch | 190 ++++++++++++++++++
recipes-containers/podman/podman_git.bb | 2 +
3 files changed, 245 insertions(+)
create mode 100644 recipes-containers/podman/podman/CVE-2026-55686-dependent.patch
create mode 100644 recipes-containers/podman/podman/CVE-2026-55686.patch
diff --git a/recipes-containers/podman/podman/CVE-2026-55686-dependent.patch b/recipes-containers/podman/podman/CVE-2026-55686-dependent.patch
new file mode 100644
index 0000000..026e5b6
--- /dev/null
+++ b/recipes-containers/podman/podman/CVE-2026-55686-dependent.patch
@@ -0,0 +1,53 @@
+From 757c798d8fd8ee2b05a7f0b5df753b37c7169a49 Mon Sep 17 00:00:00 2001
+From: Paul Holzinger <pholzing@redhat.com>
+Date: Wed, 3 Dec 2025 16:57:15 +0100
+Subject: [PATCH 1/2] libpod: fix workdir MkdirAll() all check
+
+MkdirAll can fail with EEXIST when the path is a symlink and the target
+doesn't exist. As such we should ignore the error.
+
+Note there is something fundemantal wrong here with the path access as
+it is following the symlink to the host, however it is only for a
+stat() so it is not an security issue here.
+
+Fixes: 637c264e2e ("fix issues found by nilness")
+
+CVE: CVE-2026-55686
+Upstream-Status: Backport [https://github.com/podman-container-tools/podman/commit/e576e002e9d95ae6b37265d9e0b3deaa4c43d73f]
+
+Backport Changes:
+- Omitted the libpod/container_internal_common.go hunk because
+ Scarthgap already has equivalent os.IsExist(err) handling in the
+ resolveWorkDir() os.MkdirAll() error path.
+- Kept the upstream symlinked-workdir e2e test because the CVE fix
+ commit extends this test upstream.
+
+Signed-off-by: Paul Holzinger <pholzing@redhat.com>
+(cherry picked from commit e576e002e9d95ae6b37265d9e0b3deaa4c43d73f)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ test/e2e/run_working_dir_test.go | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/test/e2e/run_working_dir_test.go b/test/e2e/run_working_dir_test.go
+index 9f06df9313..53040cd98c 100644
+--- a/test/e2e/run_working_dir_test.go
++++ b/test/e2e/run_working_dir_test.go
+@@ -56,4 +56,14 @@ WORKDIR /etc/foobar`, ALPINE)
+ Expect(session).Should(ExitCleanly())
+ Expect(session.OutputToString()).To(Equal("/home/foobar"))
+ })
++
++ It("podman run on an image with a symlinked workdir", func() {
++ dockerfile := fmt.Sprintf(`FROM %s
++RUN mkdir /A && ln -s /A /B
++WORKDIR /B`, ALPINE)
++ podmanTest.BuildImage(dockerfile, "test", "false")
++
++ session := podmanTest.PodmanExitCleanly("run", "test", "pwd")
++ Expect(session.OutputToString()).To(Equal("/A"))
++ })
+ })
+--
+2.35.6
+
diff --git a/recipes-containers/podman/podman/CVE-2026-55686.patch b/recipes-containers/podman/podman/CVE-2026-55686.patch
new file mode 100644
index 0000000..fe0744c
--- /dev/null
+++ b/recipes-containers/podman/podman/CVE-2026-55686.patch
@@ -0,0 +1,190 @@
+From 7548d6714dee9c43f437202dc3d3652e89a99ee1 Mon Sep 17 00:00:00 2001
+From: Paul Holzinger <pholzing@redhat.com>
+Date: Wed, 3 Dec 2025 19:15:24 +0100
+Subject: [PATCH 2/2] libpod: simplify resolveWorkDir()
+
+The code checks for isPathOnVolume and isPathOnMount so we can just use
+the SecureJoin here directly to check for path existance.
+
+Then instead of walking symlinks and trying to guess if they are on a
+mount just assume if it is a link (path is different from the normal
+joined one) then don't error out early and let the OCI runtime deal with
+it. The runtime does produce a less readable error but it still fails
+and we have much less fragile code.
+
+CVE: CVE-2026-55686
+Upstream-Status: Backport [https://github.com/podman-container-tools/podman/commit/7ce2e00ab140c11a68301f0b161f51984131a858]
+
+Backport Changes:
+- Applied the upstream SecureJoin-based resolveWorkDir() change to
+ Scarthgap's older v5.0 function shape, replacing its
+ isWorkDirSymlink()/resolvePath() flow with the same security behavior.
+- Kept Scarthgap's existing os.MkdirAll(..., 0755) and os.IsExist(err)
+ context because the equivalent prerequisite behavior is already present
+ and is documented in CVE-2026-55686-dependent.patch.
+- Kept Scarthgap's existing gexec import while applying the upstream
+ symlinked-workdir test extension.
+
+Signed-off-by: Paul Holzinger <pholzing@redhat.com>
+(cherry picked from commit 7ce2e00ab140c11a68301f0b161f51984131a858)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ libpod/container_internal_common.go | 81 +++++++----------------------
+ test/e2e/run_working_dir_test.go | 24 ++++++++-
+ 2 files changed, 41 insertions(+), 64 deletions(-)
+
+diff --git a/libpod/container_internal_common.go b/libpod/container_internal_common.go
+index 3bb6dfe1eb..cfdaf1a148 100644
+--- a/libpod/container_internal_common.go
++++ b/libpod/container_internal_common.go
+@@ -688,54 +688,6 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
+ return g.Config, cleanupFunc, nil
+ }
+
+-// isWorkDirSymlink returns true if resolved workdir is symlink or a chain of symlinks,
+-// and final resolved target is present either on volume, mount or inside of container
+-// otherwise it returns false. Following function is meant for internal use only and
+-// can change at any point of time.
+-func (c *Container) isWorkDirSymlink(resolvedPath string) bool {
+- // We cannot create workdir since explicit --workdir is
+- // set in config but workdir could also be a symlink.
+- // If it's a symlink, check if the resolved target is present in the container.
+- // If so, that's a valid use case: return nil.
+-
+- maxSymLinks := 0
+- for {
+- // Linux only supports a chain of 40 links.
+- // Reference: https://github.com/torvalds/linux/blob/master/include/linux/namei.h#L13
+- if maxSymLinks > 40 {
+- break
+- }
+- resolvedSymlink, err := os.Readlink(resolvedPath)
+- if err != nil {
+- // End sym-link resolution loop.
+- break
+- }
+- if resolvedSymlink != "" {
+- _, resolvedSymlinkWorkdir, err := c.resolvePath(c.state.Mountpoint, resolvedSymlink)
+- if isPathOnVolume(c, resolvedSymlinkWorkdir) || isPathOnMount(c, resolvedSymlinkWorkdir) {
+- // Resolved symlink exists on external volume or mount
+- return true
+- }
+- if err != nil {
+- // Could not resolve path so end sym-link resolution loop.
+- break
+- }
+- if resolvedSymlinkWorkdir != "" {
+- resolvedPath = resolvedSymlinkWorkdir
+- _, err := os.Stat(resolvedSymlinkWorkdir)
+- if err == nil {
+- // Symlink resolved successfully and resolved path exists on container,
+- // this is a valid use-case so return nil.
+- logrus.Debugf("Workdir is a symlink with target to %q and resolved symlink exists on container", resolvedSymlink)
+- return true
+- }
+- }
+- }
+- maxSymLinks++
+- }
+- return false
+-}
+-
+ // resolveWorkDir resolves the container's workdir and, depending on the
+ // configuration, will create it, or error out if it does not exist.
+ // Note that the container must be mounted before.
+@@ -750,7 +702,7 @@ func (c *Container) resolveWorkDir() error {
+ return nil
+ }
+
+- _, resolvedWorkdir, err := c.resolvePath(c.state.Mountpoint, workdir)
++ resolvedWorkdir, err := securejoin.SecureJoin(c.state.Mountpoint, workdir)
+ if err != nil {
+ return err
+ }
+@@ -766,20 +718,23 @@ func (c *Container) resolveWorkDir() error {
+ if !c.config.CreateWorkingDir {
+ // No need to create it (e.g., `--workdir=/foo`), so let's make sure
+ // the path exists on the container.
+- if err != nil {
+- if os.IsNotExist(err) {
+- // If resolved Workdir path gets marked as a valid symlink,
+- // return nil cause this is valid use-case.
+- if c.isWorkDirSymlink(resolvedWorkdir) {
+- return nil
+- }
+- return fmt.Errorf("workdir %q does not exist on container %s", workdir, c.ID())
+- }
+- // This might be a serious error (e.g., permission), so
+- // we need to return the full error.
+- return fmt.Errorf("detecting workdir %q on container %s: %w", workdir, c.ID(), err)
+- }
+- return nil
++ if errors.Is(err, os.ErrNotExist) {
++ // Check if path is a symlink, securejoin resolves and follows the links
++ // so the path will be different from the normal join if it is one.
++ if resolvedWorkdir != filepath.Join(c.state.Mountpoint, workdir) {
++ // Path must be a symlink to non existing directory.
++ // It could point to mounts that are only created later so that make
++ // an assumption here and let's just continue and let the oci runtime
++ // do its job.
++ return nil
++ }
++ // If they are the same we know there is no symlink/relative path involved.
++ // We can return a nicer error message without having to go through the OCI runtime.
++ return fmt.Errorf("workdir %q does not exist on container %s", workdir, c.ID())
++ }
++ // This might be a serious error (e.g., permission), so
++ // we need to return the full error.
++ return fmt.Errorf("detecting workdir %q on container %s: %w", workdir, c.ID(), err)
+ }
+ if err := os.MkdirAll(resolvedWorkdir, 0755); err != nil {
+ if os.IsExist(err) {
+diff --git a/test/e2e/run_working_dir_test.go b/test/e2e/run_working_dir_test.go
+index 53040cd98c..ff9eeac50c 100644
+--- a/test/e2e/run_working_dir_test.go
++++ b/test/e2e/run_working_dir_test.go
+@@ -9,6 +9,7 @@ import (
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
++ "github.com/onsi/gomega/types"
+ )
+
+ var _ = Describe("Podman run", func() {
+@@ -59,11 +60,32 @@ WORKDIR /etc/foobar`, ALPINE)
+
+ It("podman run on an image with a symlinked workdir", func() {
+ dockerfile := fmt.Sprintf(`FROM %s
+-RUN mkdir /A && ln -s /A /B
++RUN mkdir /A && ln -s /A /B && ln -s /vol/test /link
+ WORKDIR /B`, ALPINE)
+ podmanTest.BuildImage(dockerfile, "test", "false")
+
+ session := podmanTest.PodmanExitCleanly("run", "test", "pwd")
+ Expect(session.OutputToString()).To(Equal("/A"))
++
++ path := filepath.Join(podmanTest.TempDir, "test")
++ err := os.Mkdir(path, 0o755)
++ Expect(err).ToNot(HaveOccurred())
++
++ session = podmanTest.PodmanExitCleanly("run", "--workdir=/link", "--volume", podmanTest.TempDir+":/vol", "test", "pwd")
++ Expect(session.OutputToString()).To(Equal("/vol/test"))
++
++ // This will fail in the runtime since the target doesn't exists
++ session = podmanTest.Podman([]string{"run", "--workdir=/link", "test", "pwd"})
++ session.WaitWithDefaultTimeout()
++ var matcher types.GomegaMatcher
++ if filepath.Base(podmanTest.OCIRuntime) == "crun" {
++ matcher = ExitWithError(127, "chdir to `/link`: No such file or directory")
++ } else if filepath.Base(podmanTest.OCIRuntime) == "runc" {
++ matcher = ExitWithError(126, "mkdir /link: file exists")
++ } else {
++ // unknown runtime, just check it failed
++ matcher = Not(ExitCleanly())
++ }
++ Expect(session).Should(matcher)
+ })
+ })
+--
+2.35.6
+
diff --git a/recipes-containers/podman/podman_git.bb b/recipes-containers/podman/podman_git.bb
index 44ee9e3..c714e73 100644
--- a/recipes-containers/podman/podman_git.bb
+++ b/recipes-containers/podman/podman_git.bb
@@ -24,6 +24,8 @@ SRC_URI = " \
file://0001-Use-securejoin.SecureJoin-when-forming-userns-paths.patch;patchdir=src/import/vendor/github.com/containers/storage \
file://CVE-2025-6032.patch;patchdir=src/import \
file://CVE-2024-9341.patch;patchdir=src/import \
+ file://CVE-2026-55686-dependent.patch;patchdir=src/import \
+ file://CVE-2026-55686.patch;patchdir=src/import \
"
LICENSE = "Apache-2.0"
--
2.35.6
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [meta-virtualization][scarthgap][PATCH] podman: Fix CVE-2026-55686
2026-07-07 7:25 [meta-virtualization][scarthgap][PATCH] podman: Fix CVE-2026-55686 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-20 21:37 ` Bruce Ashfield
0 siblings, 0 replies; 2+ messages in thread
From: Bruce Ashfield @ 2026-07-20 21:37 UTC (permalink / raw)
To: deeratho, meta-virtualization
merged
Bruce
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-20 21:38 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-07 7:25 [meta-virtualization][scarthgap][PATCH] podman: Fix CVE-2026-55686 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-20 21:37 ` Bruce Ashfield
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.