All of lore.kernel.org
 help / color / mirror / Atom feed
* [meta-virtualization][wrynose][PATCH 2/2] docker-moby: Fix CVE-2026-42306
@ 2026-07-09 11:35 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-20 21:26 ` Bruce Ashfield
  0 siblings, 1 reply; 2+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-07-09 11:35 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream Moby v29 backport for CVE-2026-42306.
The upstream fix commits are referenced in [1] and [2], and the public
CVE advisory is referenced in [3].

[1] https://github.com/moby/moby/commit/43fa458a9c40873867e75221454de10709b04236
[2] https://github.com/moby/moby/commit/fb3702d033601bb0e767f2ca398e3909a15be29c
[3] https://nvd.nist.gov/vuln/detail/CVE-2026-42306

Signed-off-by: Deepak Rathore <deeratho@cisco.com>

diff --git a/recipes-containers/docker/docker-moby_git.bb b/recipes-containers/docker/docker-moby_git.bb
index 31b8589d..a512219a 100644
--- a/recipes-containers/docker/docker-moby_git.bb
+++ b/recipes-containers/docker/docker-moby_git.bb
@@ -55,6 +55,8 @@ SRC_URI = "\
         file://0001-dynbinary-use-go-cross-compiler.patch;patchdir=src/import \
         file://0001-check-config-make-CONFIG_MEMCG_SWAP-conditional.patch;patchdir=src/import \
         file://CVE-2026-41568.patch;patchdir=src/import \
+        file://CVE-2026-42306_p1.patch;patchdir=src/import \
+        file://CVE-2026-42306_p2.patch;patchdir=src/import \
 	"
 
 DOCKER_COMMIT = "${SRCREV_moby}"
diff --git a/recipes-containers/docker/files/CVE-2026-42306_p1.patch b/recipes-containers/docker/files/CVE-2026-42306_p1.patch
new file mode 100644
index 00000000..22b556c0
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2026-42306_p1.patch
@@ -0,0 +1,152 @@
+From 3816f8eb0bea8ba708ba3172739c668076002b61 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= <pawel.gronowski@docker.com>
+Date: Thu, 26 Mar 2026 18:49:45 +0100
+Subject: [PATCH] Fix bind mount target redirection via symlink swap during
+ docker cp
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Pin mount targets via /proc/self/fd file descriptors to prevent TOCTOU
+attacks.
+
+Previously, a container process could swap a path component with a
+symlink between GetResourcePath resolution and directory/file creation
+or mount, allowing writes to arbitrary host paths outside the container.
+
+Open the resolved destination through os.Root to get a pinned fd, then
+mount onto /proc/self/fd/<fd> instead of re-resolving the
+container-relative path. This closes the TOCTOU window between
+createIfNotExists and mount.
+
+Because the kernel rejects remount and propagation-change syscalls on
+/proc/self/fd paths, the initial bind mount uses the fd path for safety,
+then Readlink resolves the real path for the subsequent read-only
+remount and rprivate propagation change.
+
+CVE: CVE-2026-42306
+Upstream-Status: Backport [https://github.com/moby/moby/commit/43fa458a9c40873867e75221454de10709b04236]
+
+Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
+(cherry picked from commit 43fa458a9c40873867e75221454de10709b04236)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ daemon/containerfs_linux.go | 56 +++++++++++++++++++++++++++++--------
+ 1 file changed, 45 insertions(+), 11 deletions(-)
+
+diff --git a/daemon/containerfs_linux.go b/daemon/containerfs_linux.go
+index 00516d2b96..52f7af0339 100644
+--- a/daemon/containerfs_linux.go
++++ b/daemon/containerfs_linux.go
+@@ -7,7 +7,7 @@ import (
+ 	"os"
+ 	"path/filepath"
+ 	"runtime"
+-	"strings"
++	"strconv"
+ 
+ 	"github.com/containerd/log"
+ 	"github.com/moby/sys/mount"
+@@ -98,10 +98,14 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV
+ 			}
+ 			defer root.Close()
+ 
++			// TODO(vvoland): Refactor this after security release.
+ 			for _, m := range mounts {
+-				dest, err := ctr.GetResourcePath(m.Destination)
++				// Destination is an absolute path within container
++				// filesystem. For the os.Root to work, we need to convert it
++				// to a path relative to root fs /
++				relDest, err := filepath.Rel("/", m.Destination)
+ 				if err != nil {
+-					return err
++					return fmt.Errorf("make destination relative: %w", err)
+ 				}
+ 
+ 				var stat os.FileInfo
+@@ -109,7 +113,7 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV
+ 				if err != nil {
+ 					return err
+ 				}
+-				if err := createIfNotExists(root, strings.TrimPrefix(m.Destination, "/"), stat.IsDir()); err != nil {
++				if err := createIfNotExists(root, relDest, stat.IsDir()); err != nil {
+ 					return err
+ 				}
+ 
+@@ -117,9 +121,7 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV
+ 				if m.NonRecursive {
+ 					bindMode = "bind"
+ 				}
+-				writeMode := "ro"
+ 				if m.Writable {
+-					writeMode = "rw"
+ 					if m.ReadOnlyNonRecursive {
+ 						return errors.New("options conflict: Writable && ReadOnlyNonRecursive")
+ 					}
+@@ -131,6 +133,37 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV
+ 					return errors.New("options conflict: ReadOnlyNonRecursive && ReadOnlyForceRecursive")
+ 				}
+ 
++				// Open the mount target through os.Root so we have a
++				// file descriptor pinning the resolved inode. Using
++				// /proc/self/fd/<fd> as the mount target prevents any
++				// subsequent symlink swap from redirecting the mount.
++				targetFile, err := root.Open(relDest)
++				if err != nil {
++					return fmt.Errorf("open mount target %q: %w", m.Destination, err)
++				}
++				targetPath := "/proc/self/fd/" + strconv.FormatUint(uint64(targetFile.Fd()), 10)
++
++				// The kernel rejects remount and propagation-change syscalls
++				// when the target is a /proc/self/fd path. Only the initial
++				// bind mount works on such paths, so we perform that via the
++				// fd path for TOCTOU safety and then resolve the real path for
++				// the read-only remount and propagation change.
++				if err := mount.Mount(m.Source, targetPath, "", bindMode); err != nil {
++					targetFile.Close()
++					return err
++				}
++				realPath, err := os.Readlink(targetPath)
++				if err != nil {
++					targetFile.Close()
++					return fmt.Errorf("readlink %s: %w", targetPath, err)
++				}
++				if !m.Writable {
++					if err := mount.Mount("", realPath, "", "ro,remount,bind"); err != nil {
++						targetFile.Close()
++						return err
++					}
++				}
++
+ 				// openContainerFS() is called for temporary mounts
+ 				// outside the container. Soon these will be unmounted
+ 				// with lazy unmount option and given we have mounted
+@@ -141,20 +174,21 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV
+ 				// all these mounts rprivate.  Do not use propagation
+ 				// property of volume as that should apply only when
+ 				// mounting happens inside the container.
+-				opts := strings.Join([]string{bindMode, writeMode, "rprivate"}, ",")
+-				if err := mount.Mount(m.Source, dest, "", opts); err != nil {
++				if err := mount.MakeRPrivate(realPath); err != nil {
++					targetFile.Close()
+ 					return err
+ 				}
+ 
+ 				if !m.Writable && !m.ReadOnlyNonRecursive {
+-					if err := makeMountRRO(dest); err != nil {
++					if err := makeMountRRO(realPath); err != nil {
++						targetFile.Close()
+ 						if m.ReadOnlyForceRecursive {
+ 							return err
+-						} else {
+-							log.G(context.TODO()).WithError(err).Debugf("Failed to make %q recursively read-only", dest)
+ 						}
++						log.G(context.TODO()).WithError(err).Debugf("Failed to make %q recursively read-only", m.Destination)
+ 					}
+ 				}
++				targetFile.Close()
+ 			}
+ 
+ 			return mounttree.SwitchRoot(ctr.BaseFS)
+-- 
+2.35.6
diff --git a/recipes-containers/docker/files/CVE-2026-42306_p2.patch b/recipes-containers/docker/files/CVE-2026-42306_p2.patch
new file mode 100644
index 00000000..da00d973
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2026-42306_p2.patch
@@ -0,0 +1,136 @@
+From f143d0422d2a80b9ddb2666d2a41bfabbd0f3430 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= <pawel.gronowski@docker.com>
+Date: Tue, 19 May 2026 16:03:30 +0200
+Subject: [PATCH] daemon: resolve in-container symlinks before os.Root mount
+ ops
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The security fix in GHSA-vp62-88p7-qqf5 switched openContainerFS to
+os.Root for mount-destination operations, but stopped walking the
+destination through in-container symlinks.
+
+os.Root refuses to follow absolute symlinks, so any container whose
+image had an absolute symlink along the mount target's path (e.g. the
+common /var/run -> /run in ubuntu/alpine/busybox) broke `docker cp`.
+
+Walk m.Destination through ctr.GetResourcePath first which follows
+symlinks to get a path relative to BaseFS, then keep using os.Root for
+the actual MkdirAll/OpenFile/Open calls.
+
+CVE: CVE-2026-42306
+Upstream-Status: Backport [https://github.com/moby/moby/commit/fb3702d033601bb0e767f2ca398e3909a15be29c]
+
+Backport Changes:
+- Removed the ImageBuildOptions argument from build.Do because
+  Wrynose's helper accepts four arguments and uses default build options
+  internally.
+
+Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
+(cherry picked from commit fb3702d033601bb0e767f2ca398e3909a15be29c)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ daemon/containerfs_linux.go              | 14 ++++---
+ integration/container/copy_linux_test.go | 66 +++++++++++++++++++++++++++++++
+ 2 files changed, 76 insertions(+), 4 deletions(-)
+
+diff --git a/daemon/containerfs_linux.go b/daemon/containerfs_linux.go
+index 52f7af0339..62e77c80a6 100644
+--- a/daemon/containerfs_linux.go
++++ b/daemon/containerfs_linux.go
+@@ -100,10 +100,16 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV
+ 
+ 			// TODO(vvoland): Refactor this after security release.
+ 			for _, m := range mounts {
+-				// Destination is an absolute path within container
+-				// filesystem. For the os.Root to work, we need to convert it
+-				// to a path relative to root fs /
+-				relDest, err := filepath.Rel("/", m.Destination)
++				// Walk m.Destination through the container's symlinks before
++				// passing it to os.Root, which refuses absolute symlinks
++				// (e.g. the common /var/run -> /run). The resolution itself
++				// is lexical; subsequent os.Root operations still enforce
++				// the GHSA-vp62-88p7-qqf5 / GHSA-rg2x-37c3-w2rh protections.
++				resolved, err := ctr.GetResourcePath(m.Destination)
++				if err != nil {
++					return fmt.Errorf("resolve mount destination %q: %w", m.Destination, err)
++				}
++				relDest, err := filepath.Rel(ctr.BaseFS, resolved)
+ 				if err != nil {
+ 					return fmt.Errorf("make destination relative: %w", err)
+ 				}
+diff --git a/integration/container/copy_linux_test.go b/integration/container/copy_linux_test.go
+new file mode 100644
+index 0000000000..d3d43944a9
+--- /dev/null
++++ b/integration/container/copy_linux_test.go
+@@ -0,0 +1,66 @@
++package container
++
++import (
++	"bytes"
++	"os"
++	"path/filepath"
++	"testing"
++
++	mounttypes "github.com/moby/moby/api/types/mount"
++	"github.com/moby/moby/client"
++	"github.com/moby/moby/v2/integration/internal/build"
++	"github.com/moby/moby/v2/integration/internal/container"
++	"github.com/moby/moby/v2/internal/testutil"
++	"github.com/moby/moby/v2/internal/testutil/fakecontext"
++	"gotest.tools/v3/assert"
++	"gotest.tools/v3/skip"
++)
++
++// TestCopyWithAbsoluteSymlinkedMountTarget was introduced as a regression test
++// for https://github.com/moby/moby/issues/52653.
++//
++// The security fix in GHSA-vp62-88p7-qqf5 switched openContainerFS to use
++// os.Root for the mount-destination operations.
++// os.Root refuses to follow absolute symlinks, but distro images commonly ship
++// /var/run as an absolute symlink to /run.
++// As a result, any container with a bind mount whose target traversed such a
++// symlink (e.g. -v /host/sock:/var/run/docker.sock) made `docker cp` fail.
++func TestCopyWithAbsoluteSymlinkedMountTarget(t *testing.T) {
++	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
++	ctx := setupTest(t)
++	apiClient := testEnv.APIClient()
++
++	// Build an image with an absolute in-container symlink along the mount
++	// target path.
++	// Stock distro images expose this shape via /var/run -> /run, but we set
++	// up our own /sockets -> /root pair so the test does not depend on any
++	// particular base image's layout.
++	buildCtx := fakecontext.New(t, "",
++		fakecontext.WithDockerfile(`FROM busybox
++RUN touch /root/nil && ln -s /root /sockets
++`),
++	)
++	defer buildCtx.Close()
++	imgID := build.Do(ctx, t, apiClient, buildCtx)
++
++	// Use testutil.TempDir so the rootless daemon can access the bind-mount
++	// source: t.TempDir() creates a 0700 parent that the fake-root user
++	// cannot stat.
++	srcDir := testutil.TempDir(t)
++	assert.NilError(t, os.WriteFile(filepath.Join(srcDir, "sock"), nil, 0o644))
++
++	cid := container.Create(ctx, t, apiClient,
++		container.WithImage(imgID),
++		container.WithMount(mounttypes.Mount{
++			Type:   mounttypes.TypeBind,
++			Source: filepath.Join(srcDir, "sock"),
++			Target: "/sockets/docker.sock",
++		}),
++	)
++
++	_, err := apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{
++		DestinationPath: "/sockets/",
++		Content:         bytes.NewReader(nil),
++	})
++	assert.NilError(t, err)
++}
+-- 
+2.35.6
-- 
2.35.6



^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-07-20 21:27 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 11:35 [meta-virtualization][wrynose][PATCH 2/2] docker-moby: Fix CVE-2026-42306 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-20 21:26 ` 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.