All of lore.kernel.org
 help / color / mirror / Atom feed
* [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.