* [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.