All of lore.kernel.org
 help / color / mirror / Atom feed
* [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680
@ 2026-07-10 12:26 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 2/3] containerd: fix CVE-2026-47262 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-07-10 12:26 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream release/2.2 backport for
CVE-2026-46680. The upstream fix commit is referenced in [1],
and the public CVE advisory is referenced in [2].

[1] https://github.com/containerd/containerd/commit/d20c6267b88bfd52277337184916e293c627542a
[2] https://github.com/containerd/containerd/security/advisories/GHSA-fqw6-gf59-qr4w

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
 .../containerd/CVE-2026-46680.patch           | 112 ++++++++++++++++++
 .../containerd/containerd_git.bb              |   1 +
 2 files changed, 113 insertions(+)
 create mode 100644 recipes-containers/containerd/containerd/CVE-2026-46680.patch

diff --git a/recipes-containers/containerd/containerd/CVE-2026-46680.patch b/recipes-containers/containerd/containerd/CVE-2026-46680.patch
new file mode 100644
index 00000000..463412f9
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-46680.patch
@@ -0,0 +1,112 @@
+From 3ad9d7b797ba6190c6fc3e0635786ea611c9fa42 Mon Sep 17 00:00:00 2001
+From: LEI WANG <ssst0n3@gmail.com>
+Date: Tue, 17 Mar 2026 17:58:00 +0800
+Subject: [PATCH 1/3] oci: return explicit error for out-of-range USER values
+
+Detect strconv.ErrRange and validate uid/gid bounds to avoid falling back to username/group lookups.
+
+CVE: CVE-2026-46680
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/d20c6267b88bfd52277337184916e293c627542a]
+
+Signed-off-by: LEI WANG <ssst0n3@gmail.com>
+(cherry picked from commit 85706b6d4416d93b47033ba345d7b885a75657b4)
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit d20c6267b88bfd52277337184916e293c627542a)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ pkg/oci/spec_opts.go            | 29 +++++++++++++++++++++++++----
+ pkg/oci/spec_opts_linux_test.go | 14 +++++++++++---
+ 2 files changed, 36 insertions(+), 7 deletions(-)
+
+diff --git a/pkg/oci/spec_opts.go b/pkg/oci/spec_opts.go
+index c298e4bb2..5cc7187d7 100644
+--- a/pkg/oci/spec_opts.go
++++ b/pkg/oci/spec_opts.go
+@@ -626,14 +626,25 @@ func WithUser(userstr string) SpecOpts {
+ 			return nil
+ 		}
+ 
++		isErrRange := func(err error) bool {
++			var numErr *strconv.NumError
++			return errors.As(err, &numErr) && numErr.Err == strconv.ErrRange
++		}
++
+ 		parts := strings.Split(userstr, ":")
+ 		switch len(parts) {
+ 		case 1:
+ 			v, err := strconv.Atoi(parts[0])
+-			if err != nil || v < minUserID || v > maxUserID {
+-				// if we cannot parse as an int32 then try to see if it is a username
++			if err != nil {
++				if isErrRange(err) {
++					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
++				}
++				// Non-numeric user value; treat it as a username.
+ 				return WithUsername(userstr)(ctx, client, c, s)
+ 			}
++			if v < minUserID || v > maxUserID {
++				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
++			}
+ 			return WithUserID(uint32(v))(ctx, client, c, s)
+ 		case 2:
+ 			var (
+@@ -642,14 +653,24 @@ func WithUser(userstr string) SpecOpts {
+ 			)
+ 			var uid, gid uint32
+ 			v, err := strconv.Atoi(parts[0])
+-			if err != nil || v < minUserID || v > maxUserID {
++			if err != nil {
++				if isErrRange(err) {
++					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
++				}
+ 				username = parts[0]
++			} else if v < minUserID || v > maxUserID {
++				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
+ 			} else {
+ 				uid = uint32(v)
+ 			}
+ 			v, err = strconv.Atoi(parts[1])
+-			if err != nil || v < minGroupID || v > maxGroupID {
++			if err != nil {
++				if isErrRange(err) {
++					return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
++				}
+ 				groupname = parts[1]
++			} else if v < minGroupID || v > maxGroupID {
++				return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
+ 			} else {
+ 				gid = uint32(v)
+ 			}
+diff --git a/pkg/oci/spec_opts_linux_test.go b/pkg/oci/spec_opts_linux_test.go
+index d30f62570..3f46ca2f4 100644
+--- a/pkg/oci/spec_opts_linux_test.go
++++ b/pkg/oci/spec_opts_linux_test.go
+@@ -94,15 +94,23 @@ guest:x:100:guest
+ 		},
+ 		{
+ 			user: "405:2147483648",
+-			err:  "no groups found",
++			err:  "invalid USER value \"405:2147483648\": gid out of range",
+ 		},
+ 		{
+ 			user: "-1000",
+-			err:  "no users found",
++			err:  "invalid USER value \"-1000\": uid out of range",
+ 		},
+ 		{
+ 			user: "2147483648",
+-			err:  "no users found",
++			err:  "invalid USER value \"2147483648\": uid out of range",
++		},
++		{
++			user: "999999999999999999999999999999999999",
++			err:  "invalid USER value \"999999999999999999999999999999999999\": uid out of range",
++		},
++		{
++			user: "0:999999999999999999999999999999999999",
++			err:  "invalid USER value \"0:999999999999999999999999999999999999\": gid out of range",
+ 		},
+ 	}
+ 	for _, testCase := range testCases {
+-- 
+2.35.6
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index 51d7c2e2..ad41f248 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -10,6 +10,7 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://0001-Makefile-allow-GO_BUILD_FLAGS-to-be-externally-speci.patch \
            file://0001-build-don-t-use-gcflags-to-define-trimpath.patch \
            file://cni-containerd-net.conflist \
+           file://CVE-2026-46680.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* [meta-virtualization][wrynose][PATCH 2/3] containerd: fix CVE-2026-47262
  2026-07-10 12:26 [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-10 12:26 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-20 21:27   ` Bruce Ashfield
  2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 3/3] containerd: fix CVE-2026-53488 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-20 21:27 ` [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680 Bruce Ashfield
  2 siblings, 1 reply; 6+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-07-10 12:26 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream release/2.2 backport for
CVE-2026-47262. The upstream fix commit is referenced in [1],
and the public CVE advisory is referenced in [2].

[1] https://github.com/containerd/containerd/commit/30708e8d1142287e9c6bb839f1b3f84c71ca4485
[2] https://github.com/containerd/containerd/security/advisories/GHSA-jpcc-p29g-p8mq

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

diff --git a/recipes-containers/containerd/containerd/CVE-2026-47262.patch b/recipes-containers/containerd/containerd/CVE-2026-47262.patch
new file mode 100644
index 00000000..47f8a594
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-47262.patch
@@ -0,0 +1,276 @@
+From 35162bac80f7801b1f99e0b86d0fa3f198470999 Mon Sep 17 00:00:00 2001
+From: Chris Henzie <chrishenzie@gmail.com>
+Date: Fri, 15 May 2026 22:19:37 +0000
+Subject: [PATCH 2/3] Bound user-database file reads in openUserFile
+
+openUserFile now stats the opened file, refuses anything that is not a
+regular file, and wraps the returned fs.File so reads are capped at
+maxUserFileBytes (10 MiB). All callers of openUserFile read either
+etc/passwd or etc/group; both are regular files on real systems, well
+under the cap.
+
+The cap and the regular-file check together bound parser memory use
+when reading user-database files of unexpected shape or size.
+
+Adds tests for the cap and for the non-regular file rejection. The cap
+test covers three boundary points: a small pad (trailing entry parsed),
+a pad placing the entry's last byte exactly on the cap (still parsed),
+and a pad past the cap (read returns an "exceeds" error).
+
+CVE: CVE-2026-47262
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/30708e8d1142287e9c6bb839f1b3f84c71ca4485]
+
+Assisted-by: Antigravity
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit 7b05ec421d0a07b33964c74145b6bf5dff58f476)
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit 30708e8d1142287e9c6bb839f1b3f84c71ca4485)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ pkg/oci/spec_opts.go                  |  56 +++++++++-
+ pkg/oci/spec_opts_user_bounds_test.go | 146 ++++++++++++++++++++++++++
+ 2 files changed, 200 insertions(+), 2 deletions(-)
+ create mode 100644 pkg/oci/spec_opts_user_bounds_test.go
+
+diff --git a/pkg/oci/spec_opts.go b/pkg/oci/spec_opts.go
+index 5cc7187d7..7e695fda1 100644
+--- a/pkg/oci/spec_opts.go
++++ b/pkg/oci/spec_opts.go
+@@ -24,6 +24,7 @@ import (
+ 	"encoding/json"
+ 	"errors"
+ 	"fmt"
++	"io"
+ 	"io/fs"
+ 	"math"
+ 	"os"
+@@ -1821,10 +1822,13 @@ type readLinker interface {
+ // openUserFile attempts to open a file within the root fs.
+ // It handles cases where the file is an absolute symlink (e.g., NixOS /etc/passwd -> /nix/store/...),
+ // which triggers "path escapes from parent" errors in Go 1.24+ due to stricter os.DirFS validation.
++//
++// The returned file rejects non-regular sources and returns an error if more
++// than maxUserFileBytes are read from it.
+ func openUserFile(root fs.FS, name string) (fs.File, error) {
+ 	f, err := root.Open(name)
+ 	if err == nil {
+-		return f, nil
++		return wrapUserFile(f, name)
+ 	}
+ 
+ 	// Check if the FS implements our local ReadLink interface.
+@@ -1841,7 +1845,11 @@ func openUserFile(root fs.FS, name string) (fs.File, error) {
+ 				if rerr == nil {
+ 					// filepath.Rel might return OS-specific separators (backslashes on Windows).
+ 					// fs.Open strictly expects forward slashes, so we convert it.
+-					return root.Open(filepath.ToSlash(rel))
++					f, oerr := root.Open(filepath.ToSlash(rel))
++					if oerr != nil {
++						return nil, oerr
++					}
++					return wrapUserFile(f, name)
+ 				}
+ 			}
+ 		}
+@@ -1850,3 +1858,47 @@ func openUserFile(root fs.FS, name string) (fs.File, error) {
+ 	// Return the original error if we couldn't resolve it
+ 	return nil, err
+ }
++
++// maxUserFileBytes caps how much data is read from any user-database file
++// opened via openUserFile. Real systems keep these files well under 1 MiB;
++// 10 MiB is generous headroom while keeping peak memory during
++// user.ParsePasswd/ParseGroup bounded to single-digit MiB.
++const maxUserFileBytes = 10 << 20
++
++// wrapUserFile rejects non-regular sources and returns an fs.File that
++// errors out if more than maxUserFileBytes are read from it.
++func wrapUserFile(f fs.File, name string) (fs.File, error) {
++	info, err := f.Stat()
++	if err != nil {
++		f.Close()
++		return nil, fmt.Errorf("stat %s: %w", name, err)
++	}
++	if !info.Mode().IsRegular() {
++		f.Close()
++		return nil, fmt.Errorf("%s is not a regular file", name)
++	}
++	return &limitedFile{
++		File: f,
++		// Allow one byte past the cap so an overflow surfaces as an
++		// error rather than a silent EOF that the parser would treat as
++		// a clean end-of-file (and miss any entries past the cap).
++		r:    &io.LimitedReader{R: f, N: maxUserFileBytes + 1},
++		name: name,
++	}, nil
++}
++
++// limitedFile is an fs.File whose Read returns an error once more than
++// maxUserFileBytes have been read.
++type limitedFile struct {
++	fs.File
++	r    *io.LimitedReader
++	name string
++}
++
++func (l *limitedFile) Read(p []byte) (int, error) {
++	n, err := l.r.Read(p)
++	if l.r.N == 0 {
++		return n, fmt.Errorf("%q exceeds %d bytes", l.name, maxUserFileBytes)
++	}
++	return n, err
++}
+diff --git a/pkg/oci/spec_opts_user_bounds_test.go b/pkg/oci/spec_opts_user_bounds_test.go
+new file mode 100644
+index 000000000..54384f79a
+--- /dev/null
++++ b/pkg/oci/spec_opts_user_bounds_test.go
+@@ -0,0 +1,146 @@
++/*
++   Copyright The containerd Authors.
++
++   Licensed under the Apache License, Version 2.0 (the "License");
++   you may not use this file except in compliance with the License.
++   You may obtain a copy of the License at
++
++       http://www.apache.org/licenses/LICENSE-2.0
++
++   Unless required by applicable law or agreed to in writing, software
++   distributed under the License is distributed on an "AS IS" BASIS,
++   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++   See the License for the specific language governing permissions and
++   limitations under the License.
++*/
++
++package oci
++
++import (
++	"bytes"
++	"errors"
++	"io/fs"
++	"testing"
++	"testing/fstest"
++	"time"
++
++	"github.com/moby/sys/user"
++	"github.com/stretchr/testify/assert"
++)
++
++// TestOpenUserFileCapsReads asserts the boundary behavior of the read cap:
++// well below, ending exactly at, and past maxUserFileBytes.
++func TestOpenUserFileCapsReads(t *testing.T) {
++	t.Parallel()
++
++	beyond := []byte("\nbeyond:x:42:\n")
++
++	for _, tc := range []struct {
++		name     string
++		padBytes int
++		wantGids []uint32
++		wantErr  bool
++	}{
++		{
++			name:     "pad below cap, beyond is parsed",
++			padBytes: 100,
++			wantGids: []uint32{42},
++		},
++		{
++			name:     "beyond ends exactly at cap, is parsed",
++			padBytes: maxUserFileBytes - len(beyond),
++			wantGids: []uint32{42},
++		},
++		{
++			name:     "pad past cap, read errors out",
++			padBytes: maxUserFileBytes,
++			wantErr:  true,
++		},
++	} {
++		t.Run(tc.name, func(t *testing.T) {
++			t.Parallel()
++
++			data := append(bytes.Repeat([]byte{0}, tc.padBytes), beyond...)
++			fsys := fstest.MapFS{
++				"etc/group": &fstest.MapFile{Data: data, Mode: 0o644},
++			}
++
++			gids, err := getSupplementalGroupsFromFS(fsys, func(g user.Group) bool {
++				return g.Name == "beyond"
++			})
++			if tc.wantErr {
++				assert.ErrorContains(t, err, "exceeds")
++				return
++			}
++			assert.NoError(t, err)
++			assert.Equal(t, tc.wantGids, gids)
++		})
++	}
++}
++
++// TestOpenUserFileRejectsNonRegularFiles verifies that non-regular files
++// are refused before any byte is read from them.
++func TestOpenUserFileRejectsNonRegularFiles(t *testing.T) {
++	t.Parallel()
++
++	for _, tc := range []struct {
++		name string
++		mode fs.FileMode
++	}{
++		{name: "char device", mode: fs.ModeDevice | fs.ModeCharDevice | 0o666},
++		{name: "socket", mode: fs.ModeSocket | 0o666},
++	} {
++		t.Run(tc.name, func(t *testing.T) {
++			t.Parallel()
++
++			f := &nonRegularFile{mode: tc.mode}
++			rootFS := singleFileFS{name: "etc/group", file: f}
++
++			_, err := getSupplementalGroupsFromFS(rootFS, nil)
++			assert.Error(t, err)
++			assert.False(t, f.readCalled, "Read should not be called on non-regular file")
++		})
++	}
++}
++
++// nonRegularFile implements fs.File and reports a configurable non-regular
++// mode via Stat.
++type nonRegularFile struct {
++	mode       fs.FileMode
++	readCalled bool
++}
++
++func (f *nonRegularFile) Read([]byte) (int, error) {
++	f.readCalled = true
++	return 0, errors.New("read should not be called on non-regular file")
++}
++
++func (f *nonRegularFile) Stat() (fs.FileInfo, error) {
++	return nonRegularFileInfo{mode: f.mode}, nil
++}
++func (f *nonRegularFile) Close() error { return nil }
++
++type nonRegularFileInfo struct {
++	mode fs.FileMode
++}
++
++func (nonRegularFileInfo) Name() string        { return "group" }
++func (nonRegularFileInfo) Size() int64         { return 0 }
++func (i nonRegularFileInfo) Mode() fs.FileMode { return i.mode }
++func (nonRegularFileInfo) ModTime() time.Time  { return time.Time{} }
++func (nonRegularFileInfo) IsDir() bool         { return false }
++func (nonRegularFileInfo) Sys() any            { return nil }
++
++// singleFileFS routes a single name to a single fs.File and returns
++// fs.ErrNotExist for everything else.
++type singleFileFS struct {
++	name string
++	file fs.File
++}
++
++func (s singleFileFS) Open(name string) (fs.File, error) {
++	if name == s.name {
++		return s.file, nil
++	}
++	return nil, fs.ErrNotExist
++}
+-- 
+2.35.6
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index ad41f248..43891c2f 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -11,6 +11,7 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://0001-build-don-t-use-gcflags-to-define-trimpath.patch \
            file://cni-containerd-net.conflist \
            file://CVE-2026-46680.patch \
+           file://CVE-2026-47262.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* [meta-virtualization][wrynose][PATCH 3/3] containerd: fix CVE-2026-53488
  2026-07-10 12:26 [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 2/3] containerd: fix CVE-2026-47262 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-10 12:26 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-20 21:27   ` Bruce Ashfield
  2026-07-20 21:27 ` [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680 Bruce Ashfield
  2 siblings, 1 reply; 6+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-07-10 12:26 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream release/2.2 backport for
CVE-2026-53488. The upstream fix commit is referenced in [1],
and the public CVE advisory is referenced in [2].

[1] https://github.com/containerd/containerd/commit/b6072a49f8d3f6efc5ac9895efbb1852b16a2602
[2] https://github.com/containerd/containerd/security/advisories/GHSA-xhf5-7wjv-pqxp

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
 .../containerd/CVE-2026-53488.patch           | 340 ++++++++++++++++++
 .../containerd/containerd_git.bb              |   1 +
 2 files changed, 341 insertions(+)
 create mode 100644 recipes-containers/containerd/containerd/CVE-2026-53488.patch

diff --git a/recipes-containers/containerd/containerd/CVE-2026-53488.patch b/recipes-containers/containerd/containerd/CVE-2026-53488.patch
new file mode 100644
index 00000000..502bec74
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-53488.patch
@@ -0,0 +1,340 @@
+From 3262711688ed9b4e3cb7b9b0c8ed9f218fb3023f Mon Sep 17 00:00:00 2001
+From: Ben Cressey <ben@cressey.org>
+Date: Fri, 29 May 2026 21:33:28 +0000
+Subject: [PATCH 3/3] Do not propagate reserved labels from image configs
+
+Image config labels are copied onto the container by both the CRI
+plugin (BuildLabels) and the client's WithImageConfigLabels option
+used by `ctr run`. Labels in the containerd.io/* namespace are
+interpreted by containerd itself and labels in the io.cri-containerd*
+namespace are interpreted by the CRI plugin. An image config is not a
+trusted source for labels in either namespace.
+
+Skip labels in both reserved namespaces when copying labels from an
+image config to a container, and warn about each label skipped: an
+image that tries to set them may be attempting to alter containerd
+behavior. Oversized image labels are already skipped this way by
+the CRI plugin.
+
+Labels set explicitly by clients, for example via `ctr run --label`
+or in the CRI request, are unaffected.
+
+Verified with the CRI plugin and with `ctr run` against an image
+whose config carries labels like these: the labels are no longer
+present on the created container and a warning is logged for each.
+
+CVE: CVE-2026-53488
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/b6072a49f8d3f6efc5ac9895efbb1852b16a2602]
+
+Assisted-by: Claude Code
+Signed-off-by: Ben Cressey <ben@cressey.org>
+Signed-off-by: Samuel Karp <samuelkarp@google.com>
+(cherry picked from commit 0ec1af4cae1256d18719ca892bf66340499e8050)
+Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
+Signed-off-by: Samuel Karp <samuelkarp@google.com>
+(cherry picked from commit b6072a49f8d3f6efc5ac9895efbb1852b16a2602)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ client/container_opts.go       | 17 ++++++++
+ client/container_opts_test.go  | 75 ++++++++++++++++++++++++++++++++++
+ internal/cri/labels/labels.go  |  6 ++-
+ internal/cri/util/util.go      | 12 +++++-
+ internal/cri/util/util_test.go | 16 ++++++--
+ pkg/labels/labels.go           | 12 ++++++
+ pkg/labels/validate.go         |  9 ++++
+ pkg/labels/validate_test.go    | 17 ++++++++
+ 8 files changed, 158 insertions(+), 6 deletions(-)
+ create mode 100644 client/container_opts_test.go
+
+diff --git a/client/container_opts.go b/client/container_opts.go
+index 04f2a9062..762dcb4b9 100644
+--- a/client/container_opts.go
++++ b/client/container_opts.go
+@@ -21,14 +21,17 @@ import (
+ 	"encoding/json"
+ 	"errors"
+ 	"fmt"
++	"maps"
+ 
+ 	"github.com/containerd/containerd/v2/core/containers"
+ 	"github.com/containerd/containerd/v2/core/content"
+ 	"github.com/containerd/containerd/v2/core/images"
+ 	"github.com/containerd/containerd/v2/core/snapshots"
++	"github.com/containerd/containerd/v2/pkg/labels"
+ 	"github.com/containerd/containerd/v2/pkg/namespaces"
+ 	"github.com/containerd/containerd/v2/pkg/oci"
+ 	"github.com/containerd/errdefs"
++	"github.com/containerd/log"
+ 	"github.com/containerd/typeurl/v2"
+ 	"github.com/opencontainers/image-spec/identity"
+ 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+@@ -113,6 +116,10 @@ func WithContainerLabels(labels map[string]string) NewContainerOpts {
+ // The existing labels are cleared as this is expected to be the first
+ // operation in setting up a container's labels. Use WithAdditionalContainerLabels
+ // to add/overwrite the existing image config labels.
++//
++// Image config labels in the namespaces reserved for containerd
++// (containerd.io/) and the CRI plugin (io.cri-containerd) are not copied
++// to the container.
+ func WithImageConfigLabels(image Image) NewContainerOpts {
+ 	return func(ctx context.Context, _ *Client, c *containers.Container) error {
+ 		ic, err := image.Config(ctx)
+@@ -138,6 +145,16 @@ func WithImageConfigLabels(image Image) NewContainerOpts {
+ 		config = ociimage.Config
+ 
+ 		c.Labels = config.Labels
++		// Labels in the containerd.io/* namespace are interpreted by containerd
++		// itself, and labels in the io.cri-containerd.* namespace are interpreted
++		// by the CRI plugin, so they are not copied from untrusted image configs.
++		maps.DeleteFunc(c.Labels, func(k, _ string) bool {
++			if labels.IsReserved(k) {
++				log.G(ctx).Warnf("skipping image label %q: the label namespace is reserved for containerd; possible malicious image attempting to alter containerd behavior", k)
++				return true
++			}
++			return false
++		})
+ 		return nil
+ 	}
+ }
+diff --git a/client/container_opts_test.go b/client/container_opts_test.go
+new file mode 100644
+index 000000000..fb01e6a33
+--- /dev/null
++++ b/client/container_opts_test.go
+@@ -0,0 +1,75 @@
++/*
++   Copyright The containerd Authors.
++
++   Licensed under the Apache License, Version 2.0 (the "License");
++   you may not use this file except in compliance with the License.
++   You may obtain a copy of the License at
++
++       http://www.apache.org/licenses/LICENSE-2.0
++
++   Unless required by applicable law or agreed to in writing, software
++   distributed under the License is distributed on an "AS IS" BASIS,
++   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++   See the License for the specific language governing permissions and
++   limitations under the License.
++*/
++
++package client
++
++import (
++	"context"
++	"encoding/json"
++	"testing"
++
++	"github.com/containerd/containerd/v2/core/containers"
++	"github.com/containerd/containerd/v2/core/content"
++	"github.com/opencontainers/go-digest"
++	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
++	"github.com/stretchr/testify/assert"
++	"github.com/stretchr/testify/require"
++)
++
++// fakeImage implements the subset of Image used by WithImageConfigLabels:
++// Config returns a descriptor with the config blob inlined in Data, so the
++// content store is never consulted.
++type fakeImage struct {
++	Image
++	config ocispec.Descriptor
++}
++
++func (i fakeImage) Config(context.Context) (ocispec.Descriptor, error) {
++	return i.config, nil
++}
++
++func (i fakeImage) ContentStore() content.Store {
++	return nil
++}
++
++func TestWithImageConfigLabels(t *testing.T) {
++	blob, err := json.Marshal(ocispec.Image{
++		Config: ocispec.ImageConfig{
++			Labels: map[string]string{
++				"foo":                          "bar",
++				"containerd.io/restart.policy": "always",
++				"io.cri-containerd.kind":       "sandbox",
++			},
++		},
++	})
++	require.NoError(t, err)
++
++	img := fakeImage{
++		config: ocispec.Descriptor{
++			MediaType: ocispec.MediaTypeImageConfig,
++			Digest:    digest.FromBytes(blob),
++			Size:      int64(len(blob)),
++			Data:      blob,
++		},
++	}
++
++	var c containers.Container
++	require.NoError(t, WithImageConfigLabels(img)(t.Context(), nil, &c))
++
++	// labels in the namespaces reserved for containerd and the CRI plugin
++	// are not copied from the image config
++	assert.Equal(t, map[string]string{"foo": "bar"}, c.Labels)
++}
+diff --git a/internal/cri/labels/labels.go b/internal/cri/labels/labels.go
+index c92b9e863..d76bb1409 100644
+--- a/internal/cri/labels/labels.go
++++ b/internal/cri/labels/labels.go
+@@ -16,9 +16,13 @@
+ 
+ package labels
+ 
++import (
++	clabels "github.com/containerd/containerd/v2/pkg/labels"
++)
++
+ const (
+ 	// criContainerdPrefix is common prefix for cri-containerd
+-	criContainerdPrefix = "io.cri-containerd"
++	criContainerdPrefix = clabels.CRIContainerdPrefix
+ 	// ImageLabelKey is the label key indicating the image is managed by cri plugin.
+ 	ImageLabelKey = criContainerdPrefix + ".image"
+ 	// ImageLabelValue is the label value indicating the image is managed by cri plugin.
+diff --git a/internal/cri/util/util.go b/internal/cri/util/util.go
+index dbacd138a..fec4b3bfd 100644
+--- a/internal/cri/util/util.go
++++ b/internal/cri/util/util.go
+@@ -81,11 +81,21 @@ func GetPassthroughAnnotations(podAnnotations map[string]string,
+ 	return passthroughAnnotations
+ }
+ 
+-// BuildLabels builds the labels from config to be passed to containerd
++// BuildLabels builds the labels from config to be passed to containerd.
++// Image config labels in the namespaces reserved for containerd
++// (containerd.io/) and the CRI plugin (io.cri-containerd) are not copied
++// to the container.
+ func BuildLabels(configLabels, imageConfigLabels map[string]string, containerType string) map[string]string {
+ 	labels := make(map[string]string)
+ 
+ 	for k, v := range imageConfigLabels {
++		// Labels in the containerd.io/* namespace are interpreted by containerd
++		// itself, and labels in the io.cri-containerd.* namespace are interpreted
++		// by the CRI plugin, so they are not copied from untrusted image configs.
++		if clabels.IsReserved(k) {
++			log.L.Warnf("skipping image label %q: the label namespace is reserved for containerd; possible malicious image attempting to alter containerd behavior", k)
++			continue
++		}
+ 		if err := clabels.Validate(k, v); err == nil {
+ 			labels[k] = v
+ 		} else {
+diff --git a/internal/cri/util/util_test.go b/internal/cri/util/util_test.go
+index 3974920f2..1664feb63 100644
+--- a/internal/cri/util/util_test.go
++++ b/internal/cri/util/util_test.go
+@@ -145,21 +145,29 @@ func TestPassThroughAnnotationsFilter(t *testing.T) {
+ 
+ func TestBuildLabels(t *testing.T) {
+ 	imageConfigLabels := map[string]string{
+-		"a":          "z",
+-		"d":          "y",
+-		"long-label": strings.Repeat("example", 10000),
++		"a":                            "z",
++		"d":                            "y",
++		"long-label":                   strings.Repeat("example", 10000),
++		"containerd.io/restart.policy": "always",
++		"io.cri-containerd.image":      "managed",
+ 	}
+ 	configLabels := map[string]string{
+ 		"a": "b",
+ 		"c": "d",
++		// reserved namespaces are only filtered for image config labels, not
++		// for labels from the CRI request
++		"containerd.io/restart.status": "stopped",
+ 	}
+ 	newLabels := BuildLabels(configLabels, imageConfigLabels, crilabels.ContainerKindSandbox)
+-	assert.Len(t, newLabels, 4)
++	assert.Len(t, newLabels, 5)
+ 	assert.Equal(t, "b", newLabels["a"])
+ 	assert.Equal(t, "d", newLabels["c"])
+ 	assert.Equal(t, "y", newLabels["d"])
++	assert.Equal(t, "stopped", newLabels["containerd.io/restart.status"])
+ 	assert.Equal(t, crilabels.ContainerKindSandbox, newLabels[crilabels.ContainerKindLabel])
+ 	assert.NotContains(t, newLabels, "long-label")
++	assert.NotContains(t, newLabels, "containerd.io/restart.policy")
++	assert.NotContains(t, newLabels, "io.cri-containerd.image")
+ 
+ 	newLabels["a"] = "e"
+ 	assert.Empty(t, configLabels[crilabels.ContainerKindLabel], "should not add new labels into original label")
+diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go
+index 0f9bab5c5..ba4c245e4 100644
+--- a/pkg/labels/labels.go
++++ b/pkg/labels/labels.go
+@@ -16,6 +16,18 @@
+ 
+ package labels
+ 
++// ReservedPrefix is the prefix of the label namespace reserved for labels
++// defined and consumed by containerd itself. Labels in this namespace must
++// not be copied from untrusted sources such as image config labels. Use
++// IsReserved to check for such labels.
++const ReservedPrefix = "containerd.io/"
++
++// CRIContainerdPrefix is the prefix of the label namespace reserved for
++// labels defined and consumed by containerd's CRI plugin. Labels in this
++// namespace must not be copied from untrusted sources such as image config
++// labels. Use IsReserved to check for such labels.
++const CRIContainerdPrefix = "io.cri-containerd"
++
+ // LabelUncompressed is added to compressed layer contents.
+ // The value is digest of the uncompressed content.
+ const LabelUncompressed = "containerd.io/uncompressed"
+diff --git a/pkg/labels/validate.go b/pkg/labels/validate.go
+index 6f23cdd7c..495427bb4 100644
+--- a/pkg/labels/validate.go
++++ b/pkg/labels/validate.go
+@@ -18,6 +18,7 @@ package labels
+ 
+ import (
+ 	"fmt"
++	"strings"
+ 
+ 	"github.com/containerd/errdefs"
+ )
+@@ -39,3 +40,11 @@ func Validate(k, v string) error {
+ 	}
+ 	return nil
+ }
++
++// IsReserved returns true if the label key is in a namespace reserved for
++// containerd (ReservedPrefix) or its CRI plugin (CRIContainerdPrefix).
++// Reserved labels are interpreted by containerd and must not be copied from
++// untrusted sources such as image config labels.
++func IsReserved(k string) bool {
++	return strings.HasPrefix(k, ReservedPrefix) || strings.HasPrefix(k, CRIContainerdPrefix)
++}
+diff --git a/pkg/labels/validate_test.go b/pkg/labels/validate_test.go
+index 16be11df3..fb97e5b69 100644
+--- a/pkg/labels/validate_test.go
++++ b/pkg/labels/validate_test.go
+@@ -53,6 +53,23 @@ func TestInvalidLabels(t *testing.T) {
+ 	}
+ }
+ 
++func TestIsReserved(t *testing.T) {
++	for key, reserved := range map[string]bool{
++		"containerd.io/":               true,
++		"containerd.io/restart.status": true,
++		"containerd.io/gc.ref.content": true,
++		"io.cri-containerd":            true,
++		"io.cri-containerd.kind":       true,
++		"io.cri-containerd.image":      true,
++		"io.cri-containerdfoo":         true,
++		"containerd.io":                false,
++		"io.containerd.something":      false,
++		"com.example.app":              false,
++	} {
++		assert.Equal(t, reserved, IsReserved(key), "IsReserved(%q)", key)
++	}
++}
++
+ func TestLongKey(t *testing.T) {
+ 	key := strings.Repeat("s", keyMaxLen+1)
+ 	value := strings.Repeat("v", maxSize-len(key))
+-- 
+2.35.6
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index 43891c2f..3cf088f0 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -12,6 +12,7 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://cni-containerd-net.conflist \
            file://CVE-2026-46680.patch \
            file://CVE-2026-47262.patch \
+           file://CVE-2026-53488.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* Re: [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680
  2026-07-10 12:26 [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 2/3] containerd: fix CVE-2026-47262 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 3/3] containerd: fix CVE-2026-53488 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-20 21:27 ` Bruce Ashfield
  2 siblings, 0 replies; 6+ messages in thread
From: Bruce Ashfield @ 2026-07-20 21:27 UTC (permalink / raw)
  To: deeratho, meta-virtualization

merged

Bruce


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

* Re: [meta-virtualization][wrynose][PATCH 2/3] containerd: fix CVE-2026-47262
  2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 2/3] containerd: fix CVE-2026-47262 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-20 21:27   ` Bruce Ashfield
  0 siblings, 0 replies; 6+ messages in thread
From: Bruce Ashfield @ 2026-07-20 21:27 UTC (permalink / raw)
  To: deeratho, meta-virtualization

merged

Bruce


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

* Re: [meta-virtualization][wrynose][PATCH 3/3] containerd: fix CVE-2026-53488
  2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 3/3] containerd: fix CVE-2026-53488 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-07-20 21:27   ` Bruce Ashfield
  0 siblings, 0 replies; 6+ messages in thread
From: Bruce Ashfield @ 2026-07-20 21:27 UTC (permalink / raw)
  To: deeratho, meta-virtualization

merged

Bruce


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

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

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 12:26 [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 2/3] containerd: fix CVE-2026-47262 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-20 21:27   ` Bruce Ashfield
2026-07-10 12:26 ` [meta-virtualization][wrynose][PATCH 3/3] containerd: fix CVE-2026-53488 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-07-20 21:27   ` Bruce Ashfield
2026-07-20 21:27 ` [meta-virtualization][wrynose][PATCH 1/3] containerd: fix CVE-2026-46680 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.