[meta-virtualization][wrynose][PATCH 1/2] docker-moby: Fix CVE-2026-41568
"Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)" <[email protected]> Thu, 9 Jul 2026 04:35:27 -0700
| Newsgroups | org.yoctoproject.lists.meta-virtualization |
|---|---|
| Message-ID | <[email protected]> |
From: Deepak Rathore <[email protected]> This patch applies the upstream Moby v29 backport for CVE-2026-41568. The upstream fix commit is referenced in [1], and the public CVE advisory is referenced in [2]. [1] https://github.com/moby/moby/commit/64a22d80b93ddc1416b501b5145df02947312249 [2] https://nvd.nist.gov/vuln/detail/CVE-2026-41568 Signed-off-by: Deepak Rathore <[email protected]> diff --git a/recipes-containers/docker/docker-moby_git.bb b/recipes-containers/docker/docker-moby_git.bb index 56d94675..31b8589d 100644 --- a/recipes-containers/docker/docker-moby_git.bb +++ b/recipes-containers/docker/docker-moby_git.bb @@ -54,6 +54,7 @@ SRC_URI = "\ file://0001-cli-use-external-GO111MODULE-and-cross-compiler.patch \ 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 \ " 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 00000000..a0a1b72a --- /dev/null +++ b/recipes-containers/docker/files/CVE-2026-41568.patch @@ -0,0 +1,147 @@ +From 84ed1ac6753f538ab762f3ba81a0db1aeff6d048 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= <[email protected]> +Date: Thu, 26 Mar 2026 18:49:45 +0100 +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/64a22d80b93ddc1416b501b5145df02947312249] + +Signed-off-by: Paweł Gronowski <[email protected]> +(cherry picked from commit 64a22d80b93ddc1416b501b5145df02947312249) +Signed-off-by: Deepak Rathore <[email protected]> +--- + daemon/containerfs_linux.go | 41 +++++++++++++++++++------------- + daemon/containerfs_linux_test.go | 22 ++++++++++------- + 2 files changed, 38 insertions(+), 25 deletions(-) + +diff --git a/daemon/containerfs_linux.go b/daemon/containerfs_linux.go +index 52a362ebbc..00516d2b96 100644 +--- a/daemon/containerfs_linux.go ++++ b/daemon/containerfs_linux.go +@@ -91,6 +91,13 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV + if err := mount.MakeRSlave("/"); err != nil { + return err + } ++ ++ root, err := os.OpenRoot(ctr.BaseFS) ++ if err != nil { ++ return fmt.Errorf("open container root: %w", err) ++ } ++ defer root.Close() ++ + for _, m := range mounts { + dest, err := ctr.GetResourcePath(m.Destination) + if err != nil { +@@ -102,7 +109,7 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV + if err != nil { + return err + } +- if err := createIfNotExists(dest, stat.IsDir()); err != nil { ++ if err := createIfNotExists(root, strings.TrimPrefix(m.Destination, "/"), stat.IsDir()); err != nil { + return err + } + +@@ -254,24 +261,24 @@ func (vw *containerFSView) Stat(ctx context.Context, path string) (*containertyp + } + + // createIfNotExists creates a file or a directory only if it does not already exist. +-func createIfNotExists(dest string, isDir bool) error { +- if _, err := os.Stat(dest); err != nil { +- // FIXME(thaJeztah): this ignores any other error (which may include "dest" is of the wrong type, or permission errors). +- if os.IsNotExist(err) { +- if isDir { +- return os.MkdirAll(dest, 0o755) +- } +- if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { +- return err +- } +- f, err := os.OpenFile(dest, os.O_CREATE, 0o755) +- if err != nil { +- return err +- } +- _ = f.Close() ++// The path is scoped to root using [os.Root] to prevent symlink escape attacks. ++func createIfNotExists(root *os.Root, unsafePath string, isDir bool) error { ++ if isDir { ++ return root.MkdirAll(unsafePath, 0o755) ++ } ++ ++ parent := filepath.Dir(unsafePath) ++ if parent != "." && parent != "/" { ++ if err := root.MkdirAll(parent, 0o755); err != nil { ++ return err + } + } +- return nil ++ ++ f, err := root.OpenFile(unsafePath, os.O_CREATE|os.O_WRONLY, 0o755) ++ if err != nil { ++ return err ++ } ++ return f.Close() + } + + // makeMountRRO makes the mount recursively read-only. +diff --git a/daemon/containerfs_linux_test.go b/daemon/containerfs_linux_test.go +index 7d436442d3..715c9bce0e 100644 +--- a/daemon/containerfs_linux_test.go ++++ b/daemon/containerfs_linux_test.go +@@ -10,30 +10,36 @@ import ( + + func TestCreateIfNotExists(t *testing.T) { + t.Run("directory", func(t *testing.T) { +- toCreate := filepath.Join(t.TempDir(), "tocreate") ++ dir := t.TempDir() ++ root, err := os.OpenRoot(dir) ++ assert.NilError(t, err) ++ defer root.Close() + +- err := createIfNotExists(toCreate, true) ++ err = createIfNotExists(root, "tocreate", true) + assert.NilError(t, err) + +- fileinfo, err := os.Stat(toCreate) ++ 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(toCreate, true) ++ err = createIfNotExists(root, "tocreate", true) + assert.NilError(t, err, "Should not fail if already exists") + }) + t.Run("file", func(t *testing.T) { +- toCreate := filepath.Join(t.TempDir(), "file/to/create") ++ dir := t.TempDir() ++ root, err := os.OpenRoot(dir) ++ assert.NilError(t, err) ++ defer root.Close() + +- err := createIfNotExists(toCreate, false) ++ err = createIfNotExists(root, "file/to/create", false) + assert.NilError(t, err) + +- fileinfo, err := os.Stat(toCreate) ++ 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(toCreate, true) ++ err = createIfNotExists(root, "file/to/create", false) + assert.NilError(t, err, "Should not fail if already exists") + }) + } +-- +2.35.6 -- 2.35.6