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

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream 25.0 backport for CVE-2026-41568.
The upstream fix commit is referenced in [1], and the public CVE
advisory is referenced in [2]. The Scarthgap-specific Go 1.22 API
adaptation is documented in the embedded patch header.

[1] https://github.com/moby/moby/commit/742e28ed8d654372f0aa5b549da74c2f6e674194
[2] https://github.com/moby/moby/security/advisories/GHSA-vp62-88p7-qqf5

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
 recipes-containers/docker/docker-moby_git.bb  |   1 +
 .../docker/files/CVE-2026-41568.patch         | 275 ++++++++++++++++++
 2 files changed, 276 insertions(+)
 create mode 100644 recipes-containers/docker/files/CVE-2026-41568.patch

diff --git a/recipes-containers/docker/docker-moby_git.bb b/recipes-containers/docker/docker-moby_git.bb
index eb493a4..f9121b3 100644
--- a/recipes-containers/docker/docker-moby_git.bb
+++ b/recipes-containers/docker/docker-moby_git.bb
@@ -59,6 +59,7 @@ SRC_URI = "\
 		file://CVE-2026-33997.patch;patchdir=src/import \
 		file://CVE-2026-34040_p1.patch;patchdir=src/import \
 		file://CVE-2026-34040_p2.patch;patchdir=src/import \
+		file://CVE-2026-41568.patch;patchdir=src/import \
 	"
 
 DOCKER_COMMIT = "${SRCREV_moby}"
diff --git a/recipes-containers/docker/files/CVE-2026-41568.patch b/recipes-containers/docker/files/CVE-2026-41568.patch
new file mode 100644
index 0000000..8dffd72
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2026-41568.patch
@@ -0,0 +1,275 @@
+From 9afb2d06f1d14b71be59d6662d4da2deacfb45fc Mon Sep 17 00:00:00 2001
+From: Deepak Rathore <deeratho@cisco.com>
+Date: Tue, 30 Jun 2026 05:16:06 -0700
+Subject: [PATCH] daemon/copy: Fix symlink escape in mount destination creation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Use os.Root to scope all filesystem operations in createIfNotExists to
+the container root directory.
+
+This prevents a TOCTOU attack where a container process swaps a path
+component with a symlink between GetResourcePath resolution and
+directory/file creation, which could allow writing to arbitrary host
+paths outside the container.
+
+CVE: CVE-2026-41568
+Upstream-Status: Backport [https://github.com/moby/moby/commit/742e28ed8d654372f0aa5b549da74c2f6e674194]
+
+Backport Changes:
+- Replaced upstream os.Root operations with dirfd-relative
+  openat(2)/mkdirat(2) helpers, because Scarthgap uses Go 1.22 and
+  does not provide os.Root.
+- Kept O_NOFOLLOW while walking each path component so the backport
+  preserves the upstream root-scoped symlink protection.
+- Adapted the createIfNotExists unit test to open the container root
+  with unix.Open.
+
+(cherry picked from commit 64a22d80b93ddc1416b501b5145df02947312249)
+Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
+(cherry picked from commit 742e28ed8d654372f0aa5b549da74c2f6e674194)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ daemon/containerfs_linux.go      | 148 +++++++++++++++++++-
+ daemon/containerfs_linux_test.go |  51 +++++++
+ 2 files changed, 197 insertions(+), 2 deletions(-)
+ create mode 100644 daemon/containerfs_linux_test.go
+
+diff --git a/daemon/containerfs_linux.go b/daemon/containerfs_linux.go
+index 384c86b..ab6b199 100644
+--- a/daemon/containerfs_linux.go
++++ b/daemon/containerfs_linux.go
+@@ -19,7 +19,6 @@ import (
+ 	"github.com/docker/docker/container"
+ 	"github.com/docker/docker/internal/mounttree"
+ 	"github.com/docker/docker/internal/unshare"
+-	"github.com/docker/docker/pkg/fileutils"
+ )
+ 
+ type future struct {
+@@ -83,6 +82,13 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 			if err := mount.MakeRSlave("/"); err != nil {
+ 				return err
+ 			}
++
++			rootFd, err := unix.Open(container.BaseFS, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
++			if err != nil {
++				return fmt.Errorf("open container root: %w", err)
++			}
++			defer unix.Close(rootFd)
++
+ 			for _, m := range mounts {
+ 				dest, err := container.GetResourcePath(m.Destination)
+ 				if err != nil {
+@@ -94,7 +100,7 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 				if err != nil {
+ 					return err
+ 				}
+-				if err := fileutils.CreateIfNotExists(dest, stat.IsDir()); err != nil {
++				if err := createIfNotExists(rootFd, strings.TrimPrefix(m.Destination, "/"), stat.IsDir()); err != nil {
+ 					return err
+ 				}
+ 
+@@ -242,6 +248,144 @@ func (vw *containerFSView) Stat(ctx context.Context, path string) (*types.Contai
+ 	return stat, err
+ }
+ 
++// createIfNotExists creates a file or directory below rootFd only if it does
++// not already exist. Path components are opened dirfd-relative with
++// O_NOFOLLOW so a container cannot swap in a symlink that escapes BaseFS.
++func createIfNotExists(rootFd int, unsafePath string, isDir bool) error {
++	if isDir {
++		return mkdirAllInRoot(rootFd, unsafePath)
++	}
++
++	parentPath := filepath.Dir(unsafePath)
++	if parentPath != "." && parentPath != "/" {
++		if err := mkdirAllInRoot(rootFd, parentPath); err != nil {
++			return err
++		}
++	}
++
++	parentFd, base, closeParent, err := openParentInRoot(rootFd, unsafePath)
++	if err != nil {
++		return err
++	}
++	if closeParent {
++		defer unix.Close(parentFd)
++	}
++
++	fd, err := unix.Openat(parentFd, base, unix.O_CREAT|unix.O_WRONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0o755)
++	if err != nil {
++		return err
++	}
++	return unix.Close(fd)
++}
++
++func mkdirAllInRoot(rootFd int, unsafePath string) error {
++	parts, err := pathComponents(unsafePath)
++	if err != nil {
++		return err
++	}
++
++	currentFd := rootFd
++	closeCurrent := false
++	for _, part := range parts {
++		nextFd, err := openDirAtNoFollow(currentFd, part)
++		if os.IsNotExist(err) {
++			if err := unix.Mkdirat(currentFd, part, 0o755); err != nil && !os.IsExist(err) {
++				if closeCurrent {
++					unix.Close(currentFd)
++				}
++				return err
++			}
++			nextFd, err = openDirAtNoFollow(currentFd, part)
++		}
++		if err != nil {
++			if closeCurrent {
++				unix.Close(currentFd)
++			}
++			return err
++		}
++		if closeCurrent {
++			unix.Close(currentFd)
++		}
++		currentFd = nextFd
++		closeCurrent = true
++	}
++	if closeCurrent {
++		return unix.Close(currentFd)
++	}
++	return nil
++}
++
++func openParentInRoot(rootFd int, unsafePath string) (parentFd int, base string, closeParent bool, err error) {
++	parts, err := pathComponents(unsafePath)
++	if err != nil {
++		return -1, "", false, err
++	}
++	if len(parts) == 0 {
++		return -1, "", false, fmt.Errorf("empty path")
++	}
++	base = parts[len(parts)-1]
++	if len(parts) == 1 {
++		return rootFd, base, false, nil
++	}
++
++	parentFd, err = openDirInRoot(rootFd, filepath.Join(parts[:len(parts)-1]...))
++	if err != nil {
++		return -1, "", false, err
++	}
++	return parentFd, base, true, nil
++}
++
++func openDirInRoot(rootFd int, unsafePath string) (int, error) {
++	parts, err := pathComponents(unsafePath)
++	if err != nil {
++		return -1, err
++	}
++	if len(parts) == 0 {
++		return rootFd, nil
++	}
++
++	currentFd := rootFd
++	closeCurrent := false
++	for _, part := range parts {
++		nextFd, err := openDirAtNoFollow(currentFd, part)
++		if err != nil {
++			if closeCurrent {
++				unix.Close(currentFd)
++			}
++			return -1, err
++		}
++		if closeCurrent {
++			unix.Close(currentFd)
++		}
++		currentFd = nextFd
++		closeCurrent = true
++	}
++	return currentFd, nil
++}
++
++func openDirAtNoFollow(rootFd int, name string) (int, error) {
++	return unix.Openat(rootFd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
++}
++
++func pathComponents(unsafePath string) ([]string, error) {
++	if filepath.IsAbs(unsafePath) {
++		return nil, fmt.Errorf("absolute path %q is not scoped to container root", unsafePath)
++	}
++
++	cleanPath := filepath.Clean(unsafePath)
++	if cleanPath == "." {
++		return nil, nil
++	}
++
++	parts := strings.Split(cleanPath, string(os.PathSeparator))
++	for _, part := range parts {
++		if part == "" || part == "." || part == ".." {
++			return nil, fmt.Errorf("path %q escapes container root", unsafePath)
++		}
++	}
++	return parts, nil
++}
++
+ // makeMountRRO makes the mount recursively read-only.
+ func makeMountRRO(dest string) error {
+ 	attr := &unix.MountAttr{
+diff --git a/daemon/containerfs_linux_test.go b/daemon/containerfs_linux_test.go
+new file mode 100644
+index 0000000..08d797b
+--- /dev/null
++++ b/daemon/containerfs_linux_test.go
+@@ -0,0 +1,51 @@
++package daemon // import "github.com/docker/docker/daemon"
++
++import (
++	"os"
++	"path/filepath"
++	"testing"
++
++	"golang.org/x/sys/unix"
++	"gotest.tools/v3/assert"
++)
++
++func TestCreateIfNotExists(t *testing.T) {
++	t.Run("directory", func(t *testing.T) {
++		dir := t.TempDir()
++		rootFd := openRootFd(t, dir)
++		defer unix.Close(rootFd)
++
++		err := createIfNotExists(rootFd, "tocreate", true)
++		assert.NilError(t, err)
++
++		fileinfo, err := os.Stat(filepath.Join(dir, "tocreate"))
++		assert.NilError(t, err, "Did not create destination")
++		assert.Assert(t, fileinfo.IsDir(), "Should have been a dir, seems it's not")
++
++		err = createIfNotExists(rootFd, "tocreate", true)
++		assert.NilError(t, err, "Should not fail if already exists")
++	})
++	t.Run("file", func(t *testing.T) {
++		dir := t.TempDir()
++		rootFd := openRootFd(t, dir)
++		defer unix.Close(rootFd)
++
++		err := createIfNotExists(rootFd, "file/to/create", false)
++		assert.NilError(t, err)
++
++		fileinfo, err := os.Stat(filepath.Join(dir, "file/to/create"))
++		assert.NilError(t, err, "Did not create destination")
++
++		assert.Assert(t, !fileinfo.IsDir(), "Should have been a file, but created a directory")
++
++		err = createIfNotExists(rootFd, "file/to/create", false)
++		assert.NilError(t, err, "Should not fail if already exists")
++	})
++}
++
++func openRootFd(t *testing.T, path string) int {
++	t.Helper()
++	fd, err := unix.Open(path, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
++	assert.NilError(t, err)
++	return fd
++}
-- 
2.35.6



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

* [meta-virtualization][scarthgap][PATCH 2/2] docker-moby: Fix CVE-2026-42306
  2026-07-15 17:25 [meta-virtualization][scarthgap][PATCH 1/2] docker-moby: Fix CVE-2026-41568 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-15 17:25 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-20 21:37   ` Bruce Ashfield
  2026-07-20 21:37 ` [meta-virtualization][scarthgap][PATCH 1/2] docker-moby: Fix CVE-2026-41568 Bruce Ashfield
  1 sibling, 1 reply; 4+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-07-15 17:25 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream 25.0 backport for CVE-2026-42306.
The upstream fix commits are referenced in [1] and [2], and the
public CVE advisory is referenced in [3]. The Scarthgap-specific
Go 1.22 API adaptation is documented in the embedded patch headers.

[1] https://github.com/moby/moby/commit/63772fe8630ee74f7c45d20637f3161cc750f4da
[2] https://github.com/moby/moby/commit/f82f5a92d2ca3a2337b167bdc0a91d99a89756ef
[3] https://github.com/moby/moby/security/advisories/GHSA-rg2x-37c3-w2rh

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
 recipes-containers/docker/docker-moby_git.bb  |   2 +
 .../docker/files/CVE-2026-42306_p1.patch      | 183 ++++++++++++++++++
 .../docker/files/CVE-2026-42306_p2.patch      | 161 +++++++++++++++
 3 files changed, 346 insertions(+)
 create mode 100644 recipes-containers/docker/files/CVE-2026-42306_p1.patch
 create mode 100644 recipes-containers/docker/files/CVE-2026-42306_p2.patch

diff --git a/recipes-containers/docker/docker-moby_git.bb b/recipes-containers/docker/docker-moby_git.bb
index f9121b3..68e53b0 100644
--- a/recipes-containers/docker/docker-moby_git.bb
+++ b/recipes-containers/docker/docker-moby_git.bb
@@ -60,6 +60,8 @@ SRC_URI = "\
 		file://CVE-2026-34040_p1.patch;patchdir=src/import \
 		file://CVE-2026-34040_p2.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 0000000..c07a74a
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2026-42306_p1.patch
@@ -0,0 +1,183 @@
+From b4eb1144ac13d0a8e1b7d7e6a3d35ce97865d036 Mon Sep 17 00:00:00 2001
+From: Deepak Rathore <deeratho@cisco.com>
+Date: Tue, 30 Jun 2026 05:17:49 -0700
+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/63772fe8630ee74f7c45d20637f3161cc750f4da]
+
+Backport Changes:
+- Replaced upstream root.Open with the Scarthgap openInRoot helper,
+  which uses openat(2) and O_NOFOLLOW from a pinned container-root fd
+  because Go 1.22 does not provide os.Root.
+- Used a raw O_PATH fd and /proc/self/fd/<fd> for the initial bind
+  mount, then kept the upstream real-path remount and propagation flow.
+- Kept raw fd close handling explicit: fatal paths close before
+  returning, while non-fatal makeMountRRO errors fall through to the
+  common close so targetFd is closed exactly once.
+- Retained the strings import because pathComponents still uses
+  strings.Split in the Scarthgap openat(2) helper; upstream removes it
+  because os.Root avoids that helper.
+
+Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
+(cherry picked from commit 43fa458a9c40873867e75221454de10709b04236)
+Signed-off-by: Cory Snider <csnider@mirantis.com>
+(cherry picked from commit 63772fe8630ee74f7c45d20637f3161cc750f4da)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ daemon/containerfs_linux.go | 67 ++++++++++++++++++++++----
+ 1 file changed, 57 insertions(+), 10 deletions(-)
+
+diff --git a/daemon/containerfs_linux.go b/daemon/containerfs_linux.go
+index ab6b199..3ed4a88 100644
+--- a/daemon/containerfs_linux.go
++++ b/daemon/containerfs_linux.go
+@@ -7,6 +7,7 @@ import (
+ 	"os"
+ 	"path/filepath"
+ 	"runtime"
++	"strconv"
+ 	"strings"
+ 
+ 	"github.com/containerd/log"
+@@ -89,10 +90,14 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 			}
+ 			defer unix.Close(rootFd)
+ 
++			// TODO(vvoland): Refactor this after security release.
+ 			for _, m := range mounts {
+-				dest, err := container.GetResourcePath(m.Destination)
++				// Destination is an absolute path within container
++				// filesystem. For the root-scoped openat operations to
++				// work, 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
+@@ -100,7 +105,7 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 				if err != nil {
+ 					return err
+ 				}
+-				if err := createIfNotExists(rootFd, strings.TrimPrefix(m.Destination, "/"), stat.IsDir()); err != nil {
++				if err := createIfNotExists(rootFd, relDest, stat.IsDir()); err != nil {
+ 					return err
+ 				}
+ 
+@@ -108,9 +113,7 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 				if m.NonRecursive {
+ 					bindMode = "bind"
+ 				}
+-				writeMode := "ro"
+ 				if m.Writable {
+-					writeMode = "rw"
+ 					if m.ReadOnlyNonRecursive {
+ 						return errors.New("options conflict: Writable && ReadOnlyNonRecursive")
+ 					}
+@@ -122,6 +125,37 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 					return errors.New("options conflict: ReadOnlyNonRecursive && ReadOnlyForceRecursive")
+ 				}
+ 
++				// Open the mount target through the container 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.
++				targetFd, err := openInRoot(rootFd, relDest)
++				if err != nil {
++					return fmt.Errorf("open mount target %q: %w", m.Destination, err)
++				}
++				targetPath := "/proc/self/fd/" + strconv.Itoa(targetFd)
++
++				// 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 {
++					unix.Close(targetFd)
++					return err
++				}
++				realPath, err := os.Readlink(targetPath)
++				if err != nil {
++					unix.Close(targetFd)
++					return fmt.Errorf("readlink %s: %w", targetPath, err)
++				}
++				if !m.Writable {
++					if err := mount.Mount("", realPath, "", "ro,remount,bind"); err != nil {
++						unix.Close(targetFd)
++						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
+@@ -132,20 +166,21 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 				// 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 {
++					unix.Close(targetFd)
+ 					return err
+ 				}
+ 
+ 				if !m.Writable && !m.ReadOnlyNonRecursive {
+-					if err := makeMountRRO(dest); err != nil {
++					if err := makeMountRRO(realPath); err != nil {
+ 						if m.ReadOnlyForceRecursive {
++							unix.Close(targetFd)
+ 							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)
+ 					}
+ 				}
++				unix.Close(targetFd)
+ 			}
+ 
+ 			return mounttree.SwitchRoot(container.BaseFS)
+@@ -278,6 +313,18 @@ func createIfNotExists(rootFd int, unsafePath string, isDir bool) error {
+ 	return unix.Close(fd)
+ }
+ 
++func openInRoot(rootFd int, unsafePath string) (int, error) {
++	parentFd, base, closeParent, err := openParentInRoot(rootFd, unsafePath)
++	if err != nil {
++		return -1, err
++	}
++	if closeParent {
++		defer unix.Close(parentFd)
++	}
++
++	return unix.Openat(parentFd, base, unix.O_PATH|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
++}
++
+ func mkdirAllInRoot(rootFd int, unsafePath string) error {
+ 	parts, err := pathComponents(unsafePath)
+ 	if err != nil {
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 0000000..ef02668
--- /dev/null
+++ b/recipes-containers/docker/files/CVE-2026-42306_p2.patch
@@ -0,0 +1,161 @@
+From 5ec5db4aa88385b86df0f4e109ecabde25498b2c Mon Sep 17 00:00:00 2001
+From: Deepak Rathore <deeratho@cisco.com>
+Date: Tue, 30 Jun 2026 05:19:01 -0700
+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/f82f5a92d2ca3a2337b167bdc0a91d99a89756ef]
+
+Backport Changes:
+- Reworded upstream os.Root comments only where this branch uses the
+  Scarthgap openat(2)-based root-scoped helpers from p1.
+- Added an Fstat check after O_PATH|O_NOFOLLOW so a final symlink fd is
+  rejected, matching the intended os.Root.Open behavior.
+- Ported upstream TestCopyWithAbsoluteSymlinkedMountTarget from
+  integration/container/copy_linux_test.go into Scarthgap's
+  integration/container/copy_test.go, where the copy integration tests
+  live in this branch.
+
+Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
+(cherry picked from commit fb3702d033601bb0e767f2ca398e3909a15be29c)
+Signed-off-by: Cory Snider <csnider@mirantis.com>
+(cherry picked from commit f82f5a92d2ca3a2337b167bdc0a91d99a89756ef)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ daemon/containerfs_linux.go        | 29 +++++++++++++++++++++-----
+ integration/container/copy_test.go | 49 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 73 insertions(+), 5 deletions(-)
+
+diff --git a/daemon/containerfs_linux.go b/daemon/containerfs_linux.go
+index 3ed4a88..f7ed847 100644
+--- a/daemon/containerfs_linux.go
++++ b/daemon/containerfs_linux.go
+@@ -92,10 +92,16 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
+ 
+ 			// TODO(vvoland): Refactor this after security release.
+ 			for _, m := range mounts {
+-				// Destination is an absolute path within container
+-				// filesystem. For the root-scoped openat operations to
+-				// work, 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 root-scoped openat operations.
++				// This preserves common absolute symlinks such as
++				// /var/run -> /run while later fd-based operations still
++				// enforce the mount destination protections.
++				resolved, err := container.GetResourcePath(m.Destination)
++				if err != nil {
++					return fmt.Errorf("resolve mount destination %q: %w", m.Destination, err)
++				}
++				relDest, err := filepath.Rel(container.BaseFS, resolved)
+ 				if err != nil {
+ 					return fmt.Errorf("make destination relative: %w", err)
+ 				}
+@@ -322,7 +328,20 @@ func openInRoot(rootFd int, unsafePath string) (int, error) {
+ 		defer unix.Close(parentFd)
+ 	}
+ 
+-	return unix.Openat(parentFd, base, unix.O_PATH|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
++	fd, err := unix.Openat(parentFd, base, unix.O_PATH|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
++	if err != nil {
++		return -1, err
++	}
++	var st unix.Stat_t
++	if err := unix.Fstat(fd, &st); err != nil {
++		unix.Close(fd)
++		return -1, err
++	}
++	if st.Mode&unix.S_IFMT == unix.S_IFLNK {
++		unix.Close(fd)
++		return -1, fmt.Errorf("mount target %q is a symlink", unsafePath)
++	}
++	return fd, nil
+ }
+ 
+ func mkdirAllInRoot(rootFd int, unsafePath string) error {
+diff --git a/integration/container/copy_test.go b/integration/container/copy_test.go
+index 38d2c799f2..a03cc38b03 100644
+--- a/integration/container/copy_test.go
++++ b/integration/container/copy_test.go
+@@ -10,10 +10,13 @@ import (
+ 	"testing"
+ 
+ 	"github.com/docker/docker/api/types"
++	mounttypes "github.com/docker/docker/api/types/mount"
+ 	"github.com/docker/docker/errdefs"
++	"github.com/docker/docker/integration/internal/build"
+ 	"github.com/docker/docker/integration/internal/container"
+ 	"github.com/docker/docker/pkg/archive"
+ 	"github.com/docker/docker/pkg/jsonmessage"
++	"github.com/docker/docker/testutil"
+ 	"github.com/docker/docker/testutil/fakecontext"
+ 	"gotest.tools/v3/assert"
+ 	is "gotest.tools/v3/assert/cmp"
+@@ -129,6 +132,52 @@ func TestCopyToContainerPathIsNotDir(t *testing.T) {
+ 	assert.Check(t, is.ErrorContains(err, "not a directory"))
+ }
+ 
++// 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, "/sockets/", bytes.NewReader(nil), types.CopyToContainerOptions{})
++	assert.NilError(t, err)
++}
++
+ func TestCopyFromContainer(t *testing.T) {
+ 	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
+ 	ctx := setupTest(t)
-- 
2.35.6



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

* Re: [meta-virtualization][scarthgap][PATCH 1/2] docker-moby: Fix CVE-2026-41568
  2026-07-15 17:25 [meta-virtualization][scarthgap][PATCH 1/2] docker-moby: Fix CVE-2026-41568 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-15 17:25 ` [meta-virtualization][scarthgap][PATCH 2/2] docker-moby: Fix CVE-2026-42306 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-20 21:37 ` Bruce Ashfield
  1 sibling, 0 replies; 4+ 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] 4+ messages in thread

* Re: [meta-virtualization][scarthgap][PATCH 2/2] docker-moby: Fix CVE-2026-42306
  2026-07-15 17:25 ` [meta-virtualization][scarthgap][PATCH 2/2] docker-moby: Fix CVE-2026-42306 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-20 21:37   ` Bruce Ashfield
  0 siblings, 0 replies; 4+ 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] 4+ messages in thread

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

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 17:25 [meta-virtualization][scarthgap][PATCH 1/2] docker-moby: Fix CVE-2026-41568 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-15 17:25 ` [meta-virtualization][scarthgap][PATCH 2/2] docker-moby: Fix CVE-2026-42306 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-20 21:37   ` Bruce Ashfield
2026-07-20 21:37 ` [meta-virtualization][scarthgap][PATCH 1/2] docker-moby: Fix CVE-2026-41568 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.