All of lore.kernel.org
 help / color / mirror / Atom feed
* [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195
@ 2026-10-16 10:06 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 2/5] containerd: Fix CVE-2026-53492 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 10:06 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

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

[1] https://github.com/containerd/containerd/commit/cff57884176a1e6ba0857a417753d799958e0f46
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-50195

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

diff --git a/recipes-containers/containerd/containerd/CVE-2026-50195.patch b/recipes-containers/containerd/containerd/CVE-2026-50195.patch
new file mode 100644
index 00000000..2aad846f
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-50195.patch
@@ -0,0 +1,73 @@
+From d996462fc527969992a87e0170992a8397222c82 Mon Sep 17 00:00:00 2001
+From: Samuel Karp <samuelkarp@google.com>
+Date: Tue, 26 May 2026 16:06:58 -0700
+Subject: [PATCH 1/4] cri: do not re-tag restored checkpoints
+
+Google-Bug-Id: 508657842
+
+CVE: CVE-2026-50195
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/cff57884176a1e6ba0857a417753d799958e0f46]
+
+Signed-off-by: Samuel Karp <samuelkarp@google.com>
+(cherry picked from commit 0c0918fa8fb4d997f889a3d811603995a3a2b68a)
+Signed-off-by: Samuel Karp <samuelkarp@google.com>
+(cherry picked from commit cff57884176a1e6ba0857a417753d799958e0f46)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ .../checkpoint/checkpoint-restore-cri-test.sh   |  4 ++--
+ .../cri/server/container_checkpoint_linux.go    | 17 -----------------
+ 2 files changed, 2 insertions(+), 19 deletions(-)
+
+diff --git a/contrib/checkpoint/checkpoint-restore-cri-test.sh b/contrib/checkpoint/checkpoint-restore-cri-test.sh
+index ff1f6e6c0..44e42426b 100755
+--- a/contrib/checkpoint/checkpoint-restore-cri-test.sh
++++ b/contrib/checkpoint/checkpoint-restore-cri-test.sh
+@@ -110,7 +110,7 @@ function test_from_archive() {
+ 	fi
+ 	# Cleanup
+ 	echo "--> Cleanup images: "
+-	crictl rmi "${TEST_IMAGE}" | sed 's/^/----> \t/'
++	(crictl rmi "${TEST_IMAGE}" || true) | sed 's/^/----> \t/'
+ 	echo -n "--> Verifying container rootfs: "
+ 	crictl exec "$ctr_id" ls -la /root/testfile
+ 	if crictl exec "$ctr_id" ls -la /etc/motd >/dev/null 2>&1; then
+@@ -184,7 +184,7 @@ function test_from_oci() {
+ 	echo "--> Cleanup images: "
+ 	../../bin/ctr -n k8s.io images rm localhost/checkpoint-image:latest | sed 's/^/----> \t/'
+ 	echo "--> Cleanup images: "
+-	crictl rmi "${TEST_IMAGE}" | sed 's/^/----> \t/'
++	(crictl rmi "${TEST_IMAGE}" || true) | sed 's/^/----> \t/'
+ 	echo "--> Deleting all pods: "
+ 	crictl -t 5s rmp -fa | sed 's/^/----> \t/'
+ 	SUCCESS=1
+diff --git a/internal/cri/server/container_checkpoint_linux.go b/internal/cri/server/container_checkpoint_linux.go
+index b54963ae8..b122c392d 100644
+--- a/internal/cri/server/container_checkpoint_linux.go
++++ b/internal/cri/server/container_checkpoint_linux.go
+@@ -331,23 +331,6 @@ func (c *criService) CRImportCheckpoint(
+ 	if _, err := reference.ParseAnyReference(config.RootfsImageName); err != nil {
+ 		return "", fmt.Errorf("error parsing reference: %q is not a valid repository/tag %v", config.RootfsImageName, err)
+ 	}
+-	tagImage, err := c.client.ImageService().Get(ctx, config.RootfsImageRef)
+-	if err != nil {
+-		return "", fmt.Errorf("failed to get checkpoint base image %s: %w", config.RootfsImageRef, err)
+-	}
+-	// Second step is to tag the image with the same tag it used to have
+-	// during checkpointing. For the error that the image NAME:TAG already
+-	// exists is ignored. It could happen that NAME:TAG now belongs to
+-	// another NAME@DIGEST than during checkpointing and the restore will
+-	// happen on another image.
+-	// TODO: handle if NAME:TAG points to a different NAME@DIGEST
+-	tagImage.Name = config.RootfsImageName
+-	_, err = c.client.ImageService().Create(ctx, tagImage)
+-	if err != nil {
+-		if !errdefs.IsAlreadyExists(err) {
+-			return "", fmt.Errorf("failed to tag checkpoint base image %s with %s: %w", config.RootfsImageRef, config.RootfsImageName, err)
+-		}
+-	}
+ 
+ 	var image imagestore.Image
+ 	for i := 1; i < 500; i++ {
+-- 
+2.35.6
+
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index 3cf088f0..4e8d95ee 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -13,6 +13,7 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://CVE-2026-46680.patch \
            file://CVE-2026-47262.patch \
            file://CVE-2026-53488.patch \
+           file://CVE-2026-50195.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* [meta-virtualization][wrynose][PATCH 2/5] containerd: Fix CVE-2026-53492
  2026-10-16 10:06 [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-16 10:06 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-22 19:09   ` Bruce Ashfield
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 3/5] containerd: Fix CVE-2026-53489 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 10:06 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

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

[1] https://github.com/containerd/containerd/commit/be8460656b84c4a1a4b244a03801e9fff1e914d3
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-53492

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

diff --git a/recipes-containers/containerd/containerd/CVE-2026-53492.patch b/recipes-containers/containerd/containerd/CVE-2026-53492.patch
new file mode 100644
index 00000000..b291d94a
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-53492.patch
@@ -0,0 +1,274 @@
+From 4e572a31ab40c4c04f72c133b47b87d0d79e38c2 Mon Sep 17 00:00:00 2001
+From: Samuel Karp <samuelkarp@google.com>
+Date: Mon, 8 Jun 2026 00:58:16 +0000
+Subject: [PATCH 2/4] cri: filter CDI annotations on checkpoint restore
+
+Filter out any annotations on the checkpointed container matching
+`cdi.k8s.io/` or exactly `cdi.k8s.io` during restore to prevent
+unauthorized device restoration. When an annotation is denied, a warning
+log is generated.
+
+Tested by:
+* Unit tests for exact matching, prefix boundaries, and metadata merging
+* Complete CRI integration and checkpoint restore suite
+
+Assisted-by: Antigravity
+
+CVE: CVE-2026-53492
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/be8460656b84c4a1a4b244a03801e9fff1e914d3]
+
+Signed-off-by: Samuel Karp <samuelkarp@google.com>
+(cherry picked from commit 861ffc1097685f9ecf13adaa381aca5fdf7ef0b4)
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit be8460656b84c4a1a4b244a03801e9fff1e914d3)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ .../checkpoint/checkpoint-restore-cri-test.sh |  22 +++-
+ .../cri/server/container_checkpoint_linux.go  |  56 ++++++---
+ .../server/container_checkpoint_linux_test.go | 107 ++++++++++++++++++
+ 3 files changed, 167 insertions(+), 18 deletions(-)
+ create mode 100644 internal/cri/server/container_checkpoint_linux_test.go
+
+diff --git a/contrib/checkpoint/checkpoint-restore-cri-test.sh b/contrib/checkpoint/checkpoint-restore-cri-test.sh
+index 44e42426b..e8c5c77c1 100755
+--- a/contrib/checkpoint/checkpoint-restore-cri-test.sh
++++ b/contrib/checkpoint/checkpoint-restore-cri-test.sh
+@@ -56,6 +56,15 @@ TESTDATA=testdata
+ # shellcheck disable=SC2034
+ export CONTAINERD_ADDRESS="$TESTDIR/c.sock"
+ export CONTAINER_RUNTIME_ENDPOINT="unix:///${CONTAINERD_ADDRESS}"
++
++# Generate crictl config file with 30s timeout
++export CRI_CONFIG_FILE="${TESTDIR}/crictl.yaml"
++cat <<EOF > "${CRI_CONFIG_FILE}"
++runtime-endpoint: unix://${CONTAINERD_ADDRESS}
++image-endpoint: unix://${CONTAINERD_ADDRESS}
++timeout: 30
++EOF
++
+ TEST_IMAGE=ghcr.io/containerd/alpine
+ 
+ function test_from_archive() {
+@@ -69,9 +78,12 @@ function test_from_archive() {
+ 	echo -n "--> Start pod: "
+ 	pod_id=$(crictl runp "$POD_JSON")
+ 	echo "$pod_id"
++	CTR_JSON=$(mktemp)
++	jq '.annotations = {"cdi.k8s.io/device":"gpu","safe.annotation":"true"}' "$TESTDATA"/container_sleep.json >"$CTR_JSON"
+ 	echo -n "--> Create container: "
+-	ctr_id=$(crictl create "$pod_id" "$TESTDATA"/container_sleep.json "$POD_JSON")
++	ctr_id=$(crictl create "$pod_id" "$CTR_JSON" "$POD_JSON")
+ 	echo "$ctr_id"
++	rm -f "$CTR_JSON"
+ 	echo -n "--> Start container: "
+ 	crictl start "$ctr_id"
+ 	lines_before=$(crictl logs "$ctr_id" | wc -l)
+@@ -108,6 +120,12 @@ function test_from_archive() {
+ 			"should be larger than before checkpointing ($lines_before)"
+ 		false
+ 	fi
++	echo "--> Verifying CDI annotation filtering on restore: "
++	actual_annots=$(crictl inspect "$ctr_id" | jq -c '.status.annotations')
++	if jq -e 'has("cdi.k8s.io/device") or (has("safe.annotation") | not)' <<<"$actual_annots" >/dev/null; then
++		echo "error: CDI annotation was not filtered or safe annotation missing: $actual_annots"
++		exit 1
++	fi
+ 	# Cleanup
+ 	echo "--> Cleanup images: "
+ 	(crictl rmi "${TEST_IMAGE}" || true) | sed 's/^/----> \t/'
+@@ -192,6 +210,8 @@ function test_from_oci() {
+ 
+ cat >"${TESTDIR}/config.toml" <<EOF
+ version = 3
++[plugins."io.containerd.cri.v1.runtime"]
++  enable_cdi = false
+ [plugins."io.containerd.cri.v1.runtime".containerd]
+   default_runtime_name = "test-runtime"
+ [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.test-runtime]
+diff --git a/internal/cri/server/container_checkpoint_linux.go b/internal/cri/server/container_checkpoint_linux.go
+index b122c392d..13cd3e2e1 100644
+--- a/internal/cri/server/container_checkpoint_linux.go
++++ b/internal/cri/server/container_checkpoint_linux.go
+@@ -290,23 +290,11 @@ func (c *criService) CRImportCheckpoint(
+ 		}
+ 	}
+ 
+-	if createAnnotations != nil {
+-		// The hash also needs to be update or Kubernetes thinks the container needs to be restarted
+-		_, ok1 := createAnnotations["io.kubernetes.container.hash"]
+-		_, ok2 := originalAnnotations["io.kubernetes.container.hash"]
+-
+-		if ok1 && ok2 {
+-			originalAnnotations["io.kubernetes.container.hash"] = createAnnotations["io.kubernetes.container.hash"]
+-		}
+-
+-		// The restart count also needs to be correctly updated
+-		_, ok1 = createAnnotations["io.kubernetes.container.restartCount"]
+-		_, ok2 = originalAnnotations["io.kubernetes.container.restartCount"]
+-
+-		if ok1 && ok2 {
+-			originalAnnotations["io.kubernetes.container.restartCount"] = createAnnotations["io.kubernetes.container.restartCount"]
+-		}
+-	}
++	originalAnnotations = filterAndMergeAnnotations(
++		ctx,
++		originalAnnotations,
++		createAnnotations,
++	)
+ 
+ 	var containerdImage client.Image
+ 
+@@ -737,3 +725,37 @@ func writeSpecDumpFile(ctx context.Context, store content.Store, desc v1.Descrip
+ 
+ 	return nil
+ }
++
++func filterAndMergeAnnotations(
++	ctx context.Context,
++	checkpointAnnotations map[string]string,
++	createAnnotations map[string]string,
++) map[string]string {
++	result := make(map[string]string)
++
++	for k, v := range checkpointAnnotations {
++		if strings.HasPrefix(k, "cdi.k8s.io/") || k == "cdi.k8s.io" {
++			log.G(ctx).Warnf("Denying annotation %q in checkpoint restore", k)
++			continue
++		}
++		result[k] = v
++	}
++
++	// The hash also needs to be update or Kubernetes thinks the container needs to be restarted
++	_, ok1 := createAnnotations["io.kubernetes.container.hash"]
++	_, ok2 := result["io.kubernetes.container.hash"]
++
++	if ok1 && ok2 {
++		result["io.kubernetes.container.hash"] = createAnnotations["io.kubernetes.container.hash"]
++	}
++
++	// The restart count also needs to be correctly updated
++	_, ok1 = createAnnotations["io.kubernetes.container.restartCount"]
++	_, ok2 = result["io.kubernetes.container.restartCount"]
++
++	if ok1 && ok2 {
++		result["io.kubernetes.container.restartCount"] = createAnnotations["io.kubernetes.container.restartCount"]
++	}
++
++	return result
++}
+diff --git a/internal/cri/server/container_checkpoint_linux_test.go b/internal/cri/server/container_checkpoint_linux_test.go
+new file mode 100644
+index 000000000..3010e5233
+--- /dev/null
++++ b/internal/cri/server/container_checkpoint_linux_test.go
+@@ -0,0 +1,107 @@
++//go:build linux
++
++/*
++   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 server
++
++import (
++	"context"
++	"sync"
++	"testing"
++
++	"github.com/containerd/log"
++	"github.com/sirupsen/logrus"
++	"github.com/stretchr/testify/assert"
++)
++
++type testLogHook struct {
++	mu      sync.Mutex
++	entries []string
++}
++
++func (h *testLogHook) Levels() []logrus.Level {
++	return []logrus.Level{logrus.WarnLevel}
++}
++
++func (h *testLogHook) Fire(entry *logrus.Entry) error {
++	h.mu.Lock()
++	defer h.mu.Unlock()
++	h.entries = append(h.entries, entry.Message)
++	return nil
++}
++
++func TestFilterAndMergeAnnotations(t *testing.T) {
++	for desc, tc := range map[string]struct {
++		checkpointAnnotations map[string]string
++		createAnnotations     map[string]string
++		expectedAnnotations   map[string]string
++		expectedWarnings      []string
++	}{
++		"cdi denied prefix boundaries": {
++			checkpointAnnotations: map[string]string{
++				"cdi.k8s.io/device":   "gpu",
++				"cdi.k8s.io/":         "true",
++				"cdi.k8s.io":          "true",
++				"safe.org/cdi.k8s.io": "ignored",
++				"other":               "val",
++			},
++			expectedAnnotations: map[string]string{
++				"safe.org/cdi.k8s.io": "ignored",
++				"other":               "val",
++			},
++			expectedWarnings: []string{
++				`Denying annotation "cdi.k8s.io/device" in checkpoint restore`,
++				`Denying annotation "cdi.k8s.io/" in checkpoint restore`,
++				`Denying annotation "cdi.k8s.io" in checkpoint restore`,
++			},
++		},
++
++		"createAnnotations update kubernetes metadata if present in both": {
++			checkpointAnnotations: map[string]string{
++				"io.kubernetes.container.hash":         "old-hash",
++				"io.kubernetes.container.restartCount": "1",
++				"safe.annotation":                      "2",
++			},
++			createAnnotations: map[string]string{
++				"io.kubernetes.container.hash":         "new-hash",
++				"io.kubernetes.container.restartCount": "2",
++			},
++			expectedAnnotations: map[string]string{
++				"io.kubernetes.container.hash":         "new-hash",
++				"io.kubernetes.container.restartCount": "2",
++				"safe.annotation":                      "2",
++			},
++		},
++	} {
++		t.Run(desc, func(t *testing.T) {
++			logger := logrus.New()
++			logger.SetLevel(logrus.WarnLevel)
++			hook := &testLogHook{}
++			logger.AddHook(hook)
++			ctx := log.WithLogger(context.Background(), logrus.NewEntry(logger))
++
++			res := filterAndMergeAnnotations(
++				ctx,
++				tc.checkpointAnnotations,
++				tc.createAnnotations,
++			)
++
++			assert.Equal(t, tc.expectedAnnotations, res)
++			assert.ElementsMatch(t, tc.expectedWarnings, hook.entries)
++		})
++	}
++}
+-- 
+2.35.6
+
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index 4e8d95ee..b2e9187a 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -14,6 +14,7 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://CVE-2026-47262.patch \
            file://CVE-2026-53488.patch \
            file://CVE-2026-50195.patch \
+           file://CVE-2026-53492.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* [meta-virtualization][wrynose][PATCH 3/5] containerd: Fix CVE-2026-53489
  2026-10-16 10:06 [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 2/5] containerd: Fix CVE-2026-53492 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-16 10:06 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-22 19:09   ` Bruce Ashfield
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 4/5] containerd: Fix CVE-2026-53495 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 10:06 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

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

[1] https://github.com/containerd/containerd/commit/357652293053d0cd3ed565f718b0050aa662ae1a
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-53489

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

diff --git a/recipes-containers/containerd/containerd/CVE-2026-53489.patch b/recipes-containers/containerd/containerd/CVE-2026-53489.patch
new file mode 100644
index 00000000..74915474
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-53489.patch
@@ -0,0 +1,529 @@
+From 0e6f9d96caa978fd688a2d94140e95f348bfe74f Mon Sep 17 00:00:00 2001
+From: Brian Goff <cpuguy83@gmail.com>
+Date: Tue, 9 Jun 2026 11:37:18 -0700
+Subject: [PATCH 3/4] cri: make checkpoint restore robust to unexpected archive
+ content
+
+The CRI checkpoint restore path unpacked checkpoint archive/OCI image content
+directly into the container's persistent state directory and read files such as
+container.log back from it with a symlink-following copy. Checkpoint content is
+externally provided, so make restore more defensive about what it unpacks and
+how it reads those files back.
+
+Behavior changes:
+
+- Only unpack regular files and directories from the checkpoint archive.
+
+- Unpack checkpoint content into a dedicated <state>/ctrd-restore
+  subdirectory created fresh rather than into the state dir itself, so
+  checkpoint content cannot collide with containerd's own files (e.g.
+  the "status" blob). Restore and cleanup operate on that subdir;
+  cleanup is now a single RemoveAll of it.
+
+CVE: CVE-2026-53489
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/357652293053d0cd3ed565f718b0050aa662ae1a]
+
+Backport Changes:
+- CVE-2026-53492 and this upstream commit independently add
+  internal/cri/server/container_checkpoint_linux_test.go. Applying the
+  selected series produces an add/add conflict for that file.
+- Resolve the conflict by merging the upstream CVE-2026-53489 checkpoint
+  archive and symlink tests into the file created by CVE-2026-53492. This
+  retains both CVEs' upstream test coverage without changing security code.
+
+Signed-off-by: Brian Goff <cpuguy83@gmail.com>
+(cherry picked from commit 8196411f24065533093be4c7ad874c23b06178f3)
+Signed-off-by: Brian Goff <cpuguy83@gmail.com>
+(cherry picked from commit 357652293053d0cd3ed565f718b0050aa662ae1a)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ .../cri/server/container_checkpoint_linux.go  | 186 ++++++++++++++----
+ .../server/container_checkpoint_linux_test.go | 130 ++++++++++++
+ internal/cri/server/container_start.go        |  35 ++--
+ 3 files changed, 286 insertions(+), 65 deletions(-)
+
+diff --git a/internal/cri/server/container_checkpoint_linux.go b/internal/cri/server/container_checkpoint_linux.go
+index 13cd3e2e1..26f0d9b25 100644
+--- a/internal/cri/server/container_checkpoint_linux.go
++++ b/internal/cri/server/container_checkpoint_linux.go
+@@ -31,7 +31,6 @@ import (
+ 	"time"
+ 
+ 	crmetadata "github.com/checkpoint-restore/checkpointctl/lib"
+-	"github.com/checkpoint-restore/go-criu/v7/stats"
+ 	"github.com/checkpoint-restore/go-criu/v7/utils"
+ 	"github.com/containerd/containerd/api/types/runc/options"
+ 	"github.com/containerd/containerd/v2/client"
+@@ -56,9 +55,86 @@ import (
+ 	"github.com/opencontainers/image-spec/identity"
+ 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+ 	spec "github.com/opencontainers/runtime-spec/specs-go"
++	"golang.org/x/sys/unix"
+ 	runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
++
++	// TODO: This package import is kept to prevent merge conflicts while integrating multiple
++	// branches, specifically because this changes vendoring.
++	_ "github.com/checkpoint-restore/go-criu/v7/stats"
+ )
+ 
++// copyNoFollow copies the regular file at src to dst without following a symlink
++// at the final path component of src.
++//
++// The checkpoint code reads files (container.log, status, stats-dump, dump.log)
++// out of the container state directory, which can contain entries unpacked from a
++// checkpoint archive or OCI image. Those entries are externally provided, so they
++// are read defensively.
++//
++// src is first lstat'd (which does not follow a final-component symlink) and must
++// be a regular file; non-regular entries are rejected before src is ever opened.
++// src is then opened with O_NOFOLLOW as a belt-and-suspenders guard in case the
++// entry changes type between the lstat and the open.
++func copyNoFollow(src, dst string, perm os.FileMode) error {
++	fi, err := os.Lstat(src)
++	if err != nil {
++		return err
++	}
++	if !fi.Mode().IsRegular() {
++		return fmt.Errorf("refusing to copy %s: not a regular file", src)
++	}
++
++	in, err := os.OpenFile(src, os.O_RDONLY|unix.O_NOFOLLOW, 0)
++	if err != nil {
++		return err
++	}
++	defer in.Close()
++
++	out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
++	if err != nil {
++		return err
++	}
++	defer out.Close()
++
++	_, err = io.Copy(out, in)
++	return err
++}
++
++// checkpointArchiveEntryAllowed reports whether a tar entry from a checkpoint
++// archive may be unpacked. Legitimate checkpoint archives contain only regular
++// files and directories; other entry types (symlinks, hardlinks, device and fifo
++// nodes) are not produced by the checkpoint code and are rejected as a hardening
++// measure.
++func checkpointArchiveEntryAllowed(hdr *tar.Header) bool {
++	switch hdr.Typeflag {
++	//nolint:staticcheck // TypeRegA is deprecated but we may still receive an external tar with TypeRegA
++	case tar.TypeReg, tar.TypeRegA, tar.TypeDir, tar.TypeXGlobalHeader:
++		return true
++	default:
++		return false
++	}
++}
++
++// assertCheckpointDirSafe verifies that the populated restore directory contains
++// only regular files and directories.
++//
++// The OCI-image restore path copies checkpoint content into the restore dir with
++// fs.CopyDir, which (unlike the tar unpack filter) faithfully recreates any
++// symlinks and special files present in the image. Restore-time consumers open
++// paths under this directory, so non-regular entries are rejected before they run.
++func assertCheckpointDirSafe(root string) error {
++	return filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
++		if err != nil {
++			return err
++		}
++		// d.Type() reports the entry type without following symlinks.
++		if d.IsDir() || d.Type().IsRegular() {
++			return nil
++		}
++		return fmt.Errorf("refusing to restore checkpoint: %s is not a regular file or directory", path)
++	})
++}
++
+ // checkIfCheckpointOCIImage returns checks if the input refers to a checkpoint image.
+ // It returns the StorageImageID of the image the input resolves to, nil otherwise.
+ func (c *criService) checkIfCheckpointOCIImage(ctx context.Context, input string) (string, error) {
+@@ -187,6 +263,12 @@ func (c *criService) CRImportCheckpoint(
+ 		}(archiveFile)
+ 
+ 		filter := archive.WithFilter(func(hdr *tar.Header) (bool, error) {
++			// Reject entry types the checkpoint code never produces (symlinks,
++			// hardlinks, device/fifo nodes) so they are not recreated on disk.
++			if !checkpointArchiveEntryAllowed(hdr) {
++				log.G(ctx).Warnf("Skipping unexpected checkpoint archive entry %q (type %d)", hdr.Name, hdr.Typeflag)
++				return false, nil
++			}
+ 			// The checkpoint archive is unpacked twice if using a tar file directly.
+ 			// The first time only the metadata files are relevant to prepare the
+ 			// restore operation. This filter function ignores the large parts of
+@@ -381,16 +463,39 @@ func (c *criService) CRImportCheckpoint(
+ 		return "", err
+ 	}
+ 
++	// Confine all checkpoint content to a dedicated subdirectory of the container
++	// state dir instead of unpacking it directly into the state dir, so it cannot
++	// collide with containerd's own files there. Create it fresh; RemoveAll unlinks
++	// any pre-existing entry without following it.
++	restoreDir := filepath.Join(containerRootDir, checkpointRestoreDir)
++	if err := os.RemoveAll(restoreDir); err != nil {
++		return "", err
++	}
++	if err := os.Mkdir(restoreDir, 0o700); err != nil {
++		return "", err
++	}
++
+ 	if restoreStorageImageID != "" {
+-		if err := fs.CopyDir(containerRootDir, mountPoint); err != nil {
++		if err := fs.CopyDir(restoreDir, mountPoint); err != nil {
+ 			return "", err
+ 		}
+ 		if err := mount.UnmountAll(mountPoint, 0); err != nil {
+ 			return "", err
+ 		}
++		// fs.CopyDir recreates any symlinks/special files from the image; reject
++		// them here so restore-time consumers only ever open regular files.
++		if err := assertCheckpointDirSafe(restoreDir); err != nil {
++			return "", err
++		}
+ 	} else {
+ 		// unpack the checkpoint archive
+ 		filter := archive.WithFilter(func(hdr *tar.Header) (bool, error) {
++			// Reject entry types the checkpoint code never produces (symlinks,
++			// hardlinks, device/fifo nodes) so they are not recreated on disk.
++			if !checkpointArchiveEntryAllowed(hdr) {
++				log.G(ctx).Warnf("Skipping unexpected checkpoint archive entry %q (type %d)", hdr.Name, hdr.Typeflag)
++				return false, nil
++			}
+ 			excludePatterns := []string{
+ 				crmetadata.ConfigDumpFile,
+ 				crmetadata.SpecDumpFile,
+@@ -408,19 +513,21 @@ func (c *criService) CRImportCheckpoint(
+ 
+ 		// Start from the beginning of the checkpoint archive
+ 		archiveFile.Seek(0, 0)
+-		_, err = archive.Apply(ctx, containerRootDir, archiveFile, []archive.ApplyOpt{filter}...)
++		_, err = archive.Apply(ctx, restoreDir, archiveFile, []archive.ApplyOpt{filter}...)
+ 
+ 		if err != nil {
+-			return "", fmt.Errorf("unpacking of checkpoint archive %s failed: %w", containerRootDir, err)
++			return "", fmt.Errorf("unpacking of checkpoint archive %s failed: %w", restoreDir, err)
+ 		}
+ 	}
+-	log.G(ctx).Debugf("Unpacked checkpoint in %s", containerRootDir)
++	log.G(ctx).Debugf("Unpacked checkpoint in %s", restoreDir)
+ 
+-	// Restore container log file (if it exists)
+-	containerLog := filepath.Join(containerRootDir, "container.log")
+-	_, err = c.os.Stat(containerLog)
+-	if err == nil {
+-		if err := c.os.CopyFile(containerLog, meta.LogPath, 0600); err != nil {
++	// Restore container log file (if it exists).
++	//
++	// container.log was unpacked from a checkpoint archive/OCI image, so it is
++	// copied without following a final-component symlink.
++	containerLog := filepath.Join(restoreDir, "container.log")
++	if err := copyNoFollow(containerLog, meta.LogPath, 0600); err != nil {
++		if !errors.Is(err, os.ErrNotExist) {
+ 			return "", fmt.Errorf("restoring container log file %s failed: %w", containerLog, err)
+ 		}
+ 	}
+@@ -488,7 +595,25 @@ func (c *criService) CheckpointContainer(ctx context.Context, r *runtime.Checkpo
+ 	if err != nil {
+ 		return nil, fmt.Errorf("failed to get task for container %q: %w", r.GetContainerId(), err)
+ 	}
+-	img, err := task.Checkpoint(ctx, []client.CheckpointTaskOpts{withCheckpointOpts(i.Runtime.Name, c.getContainerRootDir(r.GetContainerId()))}...)
++
++	cpPath := filepath.Join(c.getContainerRootDir(container.ID), "ctrd-checkpoint")
++	// ctrd-checkpoint may already exist from a prior checkpoint operation. RemoveAll
++	// unlinks any existing entry (including a symlink) itself rather than its target,
++	// so creating the directory afterwards cannot write through a link.
++	if err := os.RemoveAll(cpPath); err != nil {
++		return nil, err
++	}
++	if err := os.Mkdir(cpPath, 0o700); err != nil {
++		return nil, err
++	}
++	defer os.RemoveAll(cpPath)
++
++	// Point CRIU's work directory (where it writes dump.log and stats-dump) at the
++	// dedicated, freshly-created checkpoint dir instead of the persistent container
++	// state dir. Otherwise checkpoint creation litters those files into the state
++	// dir where they are never cleaned up; here they land directly where they are
++	// archived from and are removed with cpPath.
++	img, err := task.Checkpoint(ctx, []client.CheckpointTaskOpts{withCheckpointOpts(i.Runtime.Name, cpPath)}...)
+ 	if err != nil {
+ 		return nil, fmt.Errorf("checkpointing container %q failed: %w", r.GetContainerId(), err)
+ 	}
+@@ -513,43 +638,20 @@ func (c *criService) CheckpointContainer(ctx context.Context, r *runtime.Checkpo
+ 		return nil, fmt.Errorf("failed to unmarshall blob into checkpoint data OCI index: %w", err)
+ 	}
+ 
+-	cpPath := filepath.Join(c.getContainerRootDir(r.GetContainerId()), "ctrd-checkpoint")
+-	if err := os.MkdirAll(cpPath, 0o700); err != nil {
+-		return nil, err
+-	}
+-	defer os.RemoveAll(cpPath)
+-
+-	// This internal containerd file is used by checkpointctl for
+-	// checkpoint archive analysis.
+-	if err := c.os.CopyFile(
+-		filepath.Join(c.getContainerRootDir(r.GetContainerId()), crmetadata.StatusFile),
++	// This internal containerd file is used by checkpointctl for checkpoint archive
++	// analysis. It lives in the container state dir, which can hold files from a
++	// prior checkpoint operation, so it is read without following symlinks.
++	if err := copyNoFollow(
++		filepath.Join(c.getContainerRootDir(container.ID), crmetadata.StatusFile),
+ 		filepath.Join(cpPath, crmetadata.StatusFile),
+ 		0o600,
+ 	); err != nil {
+ 		return nil, err
+ 	}
+ 
+-	// This file is created by CRIU and includes timing analysis.
+-	// Also used by checkpointctl
+-	if err := c.os.CopyFile(
+-		filepath.Join(c.getContainerRootDir(r.GetContainerId()), stats.StatsDump),
+-		filepath.Join(cpPath, stats.StatsDump),
+-		0o600,
+-	); err != nil {
+-		return nil, err
+-	}
+-
+-	// The log file created by CRIU. This file could be missing.
+-	// Let's ignore errors if the file is missing.
+-	if err := c.os.CopyFile(
+-		filepath.Join(c.getContainerRootDir(r.GetContainerId()), crmetadata.DumpLogFile),
+-		filepath.Join(cpPath, crmetadata.DumpLogFile),
+-		0o600,
+-	); err != nil {
+-		if !errors.Is(errors.Unwrap(err), os.ErrNotExist) {
+-			return nil, err
+-		}
+-	}
++	// dump.log and stats-dump are written directly into cpPath by CRIU via its
++	// work directory (see withCheckpointOpts above), so they are already present
++	// for archiving and do not need to be copied out of the container state dir.
+ 
+ 	// Save the existing container log file
+ 	_, err = c.os.Stat(criContainerStatus.GetStatus().GetLogPath())
+diff --git a/internal/cri/server/container_checkpoint_linux_test.go b/internal/cri/server/container_checkpoint_linux_test.go
+index 3010e5233..740a52100 100644
+--- a/internal/cri/server/container_checkpoint_linux_test.go
++++ b/internal/cri/server/container_checkpoint_linux_test.go
+@@ -19,13 +19,19 @@
+ package server
+ 
+ import (
++	"archive/tar"
+ 	"context"
++	"errors"
++	"os"
++	"path/filepath"
+ 	"sync"
+ 	"testing"
+ 
+ 	"github.com/containerd/log"
+ 	"github.com/sirupsen/logrus"
+ 	"github.com/stretchr/testify/assert"
++	"github.com/stretchr/testify/require"
++	"golang.org/x/sys/unix"
+ )
+ 
+ type testLogHook struct {
+@@ -105,3 +111,127 @@ func TestFilterAndMergeAnnotations(t *testing.T) {
+ 		})
+ 	}
+ }
++
++func TestCopyNoFollowRegularFile(t *testing.T) {
++	dir := t.TempDir()
++	src := filepath.Join(dir, "src")
++	dst := filepath.Join(dir, "dst")
++	require.NoError(t, os.WriteFile(src, []byte("hello"), 0o644))
++
++	require.NoError(t, copyNoFollow(src, dst, 0o600))
++
++	data, err := os.ReadFile(dst)
++	require.NoError(t, err)
++	assert.Equal(t, "hello", string(data))
++
++	info, err := os.Stat(dst)
++	require.NoError(t, err)
++	assert.Equal(t, os.FileMode(0o600), info.Mode().Perm())
++}
++
++func TestCopyNoFollowMissingSource(t *testing.T) {
++	dir := t.TempDir()
++	src := filepath.Join(dir, "does-not-exist")
++	dst := filepath.Join(dir, "dst")
++
++	err := copyNoFollow(src, dst, 0o600)
++	require.Error(t, err)
++	assert.True(t, errors.Is(err, os.ErrNotExist), "expected ErrNotExist, got %v", err)
++	assert.NoFileExists(t, dst)
++}
++
++func TestCopyNoFollowSymlinkSourceNotFollowed(t *testing.T) {
++	dir := t.TempDir()
++
++	// A stand-in for a file outside the copy that a symlink might point at.
++	secret := filepath.Join(dir, "outside-target")
++	require.NoError(t, os.WriteFile(secret, []byte("outside-content"), 0o600))
++
++	src := filepath.Join(dir, "container.log")
++	require.NoError(t, os.Symlink(secret, src))
++	dst := filepath.Join(dir, "dst")
++
++	err := copyNoFollow(src, dst, 0o600)
++	require.Error(t, err)
++	// A symlink is not a "missing file"; it must surface as an error, not be skipped.
++	assert.False(t, errors.Is(err, os.ErrNotExist))
++	// And the linked-to content must not have been copied into the destination.
++	assert.NoFileExists(t, dst)
++}
++
++func TestCopyNoFollowRejectsFIFO(t *testing.T) {
++	dir := t.TempDir()
++	src := filepath.Join(dir, "fifo")
++	require.NoError(t, unix.Mkfifo(src, 0o600))
++	dst := filepath.Join(dir, "dst")
++
++	// Must return promptly with an error rather than blocking on the FIFO open.
++	err := copyNoFollow(src, dst, 0o600)
++	require.Error(t, err)
++	assert.False(t, errors.Is(err, os.ErrNotExist))
++	assert.NoFileExists(t, dst)
++}
++
++func TestCopyNoFollowRejectsDirectory(t *testing.T) {
++	dir := t.TempDir()
++	src := filepath.Join(dir, "adir")
++	require.NoError(t, os.Mkdir(src, 0o700))
++	dst := filepath.Join(dir, "dst")
++
++	err := copyNoFollow(src, dst, 0o600)
++	require.Error(t, err)
++	assert.NoFileExists(t, dst)
++}
++
++func TestAssertCheckpointDirSafe(t *testing.T) {
++	t.Run("regular files and dirs allowed", func(t *testing.T) {
++		root := t.TempDir()
++		require.NoError(t, os.MkdirAll(filepath.Join(root, "checkpoint"), 0o700))
++		require.NoError(t, os.WriteFile(filepath.Join(root, "checkpoint", "img"), []byte("x"), 0o600))
++		require.NoError(t, os.WriteFile(filepath.Join(root, "rootfs-diff.tar"), []byte("x"), 0o600))
++		assert.NoError(t, assertCheckpointDirSafe(root))
++	})
++
++	t.Run("symlink rejected", func(t *testing.T) {
++		root := t.TempDir()
++		require.NoError(t, os.Symlink("/some/outside/path", filepath.Join(root, "rootfs-diff.tar")))
++		assert.Error(t, assertCheckpointDirSafe(root))
++	})
++
++	t.Run("symlink nested in subdir rejected", func(t *testing.T) {
++		root := t.TempDir()
++		require.NoError(t, os.MkdirAll(filepath.Join(root, "checkpoint"), 0o700))
++		require.NoError(t, os.Symlink("/some/outside/path", filepath.Join(root, "checkpoint", "pages-1.img")))
++		assert.Error(t, assertCheckpointDirSafe(root))
++	})
++
++	t.Run("fifo rejected", func(t *testing.T) {
++		root := t.TempDir()
++		require.NoError(t, unix.Mkfifo(filepath.Join(root, "fifo"), 0o600))
++		assert.Error(t, assertCheckpointDirSafe(root))
++	})
++}
++
++func TestCheckpointArchiveEntryAllowed(t *testing.T) {
++	for _, tc := range []struct {
++		name    string
++		typ     byte
++		allowed bool
++	}{
++		{"regular", tar.TypeReg, true},
++		//nolint:staticcheck // TypeRegA is deprecated but external tars may still use it
++		{"regular-A", tar.TypeRegA, true},
++		{"directory", tar.TypeDir, true},
++		{"global-header", tar.TypeXGlobalHeader, true},
++		{"symlink", tar.TypeSymlink, false},
++		{"hardlink", tar.TypeLink, false},
++		{"char-device", tar.TypeChar, false},
++		{"block-device", tar.TypeBlock, false},
++		{"fifo", tar.TypeFifo, false},
++	} {
++		t.Run(tc.name, func(t *testing.T) {
++			got := checkpointArchiveEntryAllowed(&tar.Header{Typeflag: tc.typ, Name: tc.name})
++			assert.Equal(t, tc.allowed, got)
++		})
++	}
++}
+diff --git a/internal/cri/server/container_start.go b/internal/cri/server/container_start.go
+index c0fb8c50f..0c7653cad 100644
+--- a/internal/cri/server/container_start.go
++++ b/internal/cri/server/container_start.go
+@@ -31,7 +31,6 @@ import (
+ 	runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
+ 
+ 	crmetadata "github.com/checkpoint-restore/checkpointctl/lib"
+-	"github.com/checkpoint-restore/go-criu/v7/stats"
+ 	containerd "github.com/containerd/containerd/v2/client"
+ 	cio "github.com/containerd/containerd/v2/internal/cri/io"
+ 	containerstore "github.com/containerd/containerd/v2/internal/cri/store/container"
+@@ -41,6 +40,12 @@ import (
+ 	cioutil "github.com/containerd/containerd/v2/pkg/ioutil"
+ )
+ 
++// checkpointRestoreDir is the subdirectory under a container's persistent state
++// directory into which checkpoint content (CRIU images, container.log,
++// rootfs-diff.tar, ...) is unpacked during restore. Confining it here keeps
++// checkpoint content from colliding with containerd's own files in the state dir.
++const checkpointRestoreDir = "ctrd-restore"
++
+ // StartContainer starts the container.
+ func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContainerRequest) (retRes *runtime.StartContainerResponse, retErr error) {
+ 	span := tracing.SpanFromContext(ctx)
+@@ -112,7 +117,7 @@ func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContain
+ 		pid, err := container.Restore(
+ 			ctx,
+ 			ioCreation,
+-			filepath.Join(c.getContainerRootDir(r.GetContainerId()), crmetadata.CheckpointDirectory),
++			filepath.Join(c.getContainerRootDir(r.GetContainerId()), checkpointRestoreDir, crmetadata.CheckpointDirectory),
+ 		)
+ 
+ 		if err != nil {
+@@ -155,28 +160,12 @@ func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContain
+ 		// It handles the TaskExit event and update container state after this.
+ 		c.startContainerExitMonitor(context.Background(), id, task.Pid(), exitCh)
+ 
+-		// cleanup checkpoint artifacts after restore
+-		cleanup := [...]string{
+-			crmetadata.RestoreLogFile,
+-			crmetadata.DumpLogFile,
+-			stats.StatsDump,
+-			stats.StatsRestore,
+-			crmetadata.NetworkStatusFile,
+-			crmetadata.RootFsDiffTar,
+-			crmetadata.DeletedFilesFile,
+-			crmetadata.CheckpointDirectory,
+-			crmetadata.StatusDumpFile,
+-			crmetadata.ConfigDumpFile,
+-			crmetadata.SpecDumpFile,
+-			"container.log",
+-		}
+-		for _, del := range cleanup {
+-			file := filepath.Join(c.getContainerRootDir(r.GetContainerId()), del)
+-			err = os.RemoveAll(file)
+-			if err != nil {
+-				log.G(ctx).Infof("Non-fatal: removal of checkpoint file (%s) failed: %v", file, err)
+-			}
++		// cleanup checkpoint artifacts after restore.
++		restoreDir := filepath.Join(c.getContainerRootDir(r.GetContainerId()), checkpointRestoreDir)
++		if err := os.RemoveAll(restoreDir); err != nil {
++			log.G(ctx).Warnf("Non-fatal: removal of checkpoint restore dir (%s) failed: %v", restoreDir, err)
+ 		}
++
+ 		log.G(ctx).Infof("Restored container %s successfully", r.GetContainerId())
+ 		return &runtime.StartContainerResponse{}, nil
+ 	}
+-- 
+2.35.6
+
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index b2e9187a..af756ba8 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -15,6 +15,7 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://CVE-2026-53488.patch \
            file://CVE-2026-50195.patch \
            file://CVE-2026-53492.patch \
+           file://CVE-2026-53489.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* [meta-virtualization][wrynose][PATCH 4/5] containerd: Fix CVE-2026-53495
  2026-10-16 10:06 [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 2/5] containerd: Fix CVE-2026-53492 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 3/5] containerd: Fix CVE-2026-53489 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-16 10:06 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-22 19:09   ` Bruce Ashfield
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 5/5] containerd: Fix CVE-2026-53493 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-22 19:09 ` [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Bruce Ashfield
  4 siblings, 1 reply; 10+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 10:06 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

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

[1] https://github.com/containerd/containerd/commit/22ccf4314d1fe0834f8e28f10d37d5305ef9880c
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-53495

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

diff --git a/recipes-containers/containerd/containerd/CVE-2026-53495.patch b/recipes-containers/containerd/containerd/CVE-2026-53495.patch
new file mode 100644
index 00000000..c22a39ca
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-53495.patch
@@ -0,0 +1,108 @@
+From dd4821d9a9995c3a1fd17ae82b3484357f5424d2 Mon Sep 17 00:00:00 2001
+From: XlabAI <xlabai@tencent.com>
+Date: Tue, 9 Jun 2026 12:17:23 +0800
+Subject: [PATCH 4/4] cri: cancel ExecSync IO drain on context cancellation
+
+When drain_exec_sync_io_timeout is 0 and a child process holds the
+pipe, drainExecSyncIO blocks forever even after the client disconnects.
+Add ctx.Done() to the select to allow cancellation, and use a deferred
+context for Process.Delete cleanup when the original context is already
+canceled.
+
+CVE: CVE-2026-53495
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/22ccf4314d1fe0834f8e28f10d37d5305ef9880c]
+
+Signed-off-by: Guannan Wang <wgnbuaa@gmail.com>
+(cherry picked from commit ff39a972369e2f12fae561a58d658bbf8f2bc318)
+Signed-off-by: Samuel Karp <samuelkarp@google.com>
+(cherry picked from commit 22ccf4314d1fe0834f8e28f10d37d5305ef9880c)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ internal/cri/server/container_execsync.go     | 13 ++++++-
+ .../cri/server/container_execsync_test.go     | 34 +++++++++++++++++++
+ 2 files changed, 46 insertions(+), 1 deletion(-)
+
+diff --git a/internal/cri/server/container_execsync.go b/internal/cri/server/container_execsync.go
+index d86b4f677..190cd92c7 100644
+--- a/internal/cri/server/container_execsync.go
++++ b/internal/cri/server/container_execsync.go
+@@ -311,19 +311,30 @@ func drainExecSyncIO(ctx context.Context, execProcess containerd.Process, drainE
+ 
+ 	select {
+ 	case <-timerCh:
++	case <-ctx.Done():
+ 	case <-attachDone:
+ 		log.G(ctx).Tracef("Stream pipe for exec process %q done", execProcess.ID())
+ 		return nil
+ 	}
+ 
+ 	log.G(ctx).Debugf("Exec process %q exits but the io is still held by other processes. Trying to delete exec process to release io", execProcess.ID())
+-	_, err := execProcess.Delete(ctx, containerd.WithProcessKill)
++	deleteCtx := ctx
++	if ctx.Err() != nil {
++		var cancel context.CancelFunc
++		deleteCtx, cancel = util.DeferContext()
++		defer cancel()
++	}
++	_, err := execProcess.Delete(deleteCtx, containerd.WithProcessKill)
+ 	if err != nil {
+ 		if !errdefs.IsNotFound(err) {
+ 			return fmt.Errorf("failed to release exec io by deleting exec process %q: %w",
+ 				execProcess.ID(), err)
+ 		}
+ 	}
++	if err := ctx.Err(); err != nil {
++		return fmt.Errorf("failed to drain exec process %q io before context cancellation: %w",
++			execProcess.ID(), err)
++	}
+ 	return fmt.Errorf("failed to drain exec process %q io in %s because io is still held by other processes",
+ 		execProcess.ID(), drainExecIOTimeout)
+ }
+diff --git a/internal/cri/server/container_execsync_test.go b/internal/cri/server/container_execsync_test.go
+index 0ef5f1c1b..51467638f 100644
+--- a/internal/cri/server/container_execsync_test.go
++++ b/internal/cri/server/container_execsync_test.go
+@@ -83,6 +83,40 @@ func TestDrainExecSyncIO(t *testing.T) {
+ 		assert.Error(t, drainExecSyncIO(ctx, ep, 3*time.Second, attachDoneCh))
+ 		assert.Equal(t, []string{"Delete"}, ep.actionEvents)
+ 	})
++
++	t.Run("CanceledNoTimeout", func(t *testing.T) {
++		ctx, cancel := context.WithCancel(ctx)
++		cancel()
++
++		ep := &fakeExecProcess{
++			id:  t.Name(),
++			pid: uint32(os.Getpid()),
++		}
++
++		attachDoneCh := make(chan struct{})
++		assert.ErrorIs(t, drainExecSyncIO(ctx, ep, 0, attachDoneCh), context.Canceled)
++		assert.Equal(t, []string{"Delete"}, ep.actionEvents)
++	})
++
++	t.Run("CancelWhileDraining", func(t *testing.T) {
++		ctx, cancel := context.WithCancel(ctx)
++
++		ep := &fakeExecProcess{
++			id:  t.Name(),
++			pid: uint32(os.Getpid()),
++		}
++
++		attachDoneCh := make(chan struct{})
++		errCh := make(chan error, 1)
++		go func() {
++			errCh <- drainExecSyncIO(ctx, ep, 0, attachDoneCh)
++		}()
++
++		time.AfterFunc(100*time.Millisecond, cancel)
++
++		assert.ErrorIs(t, <-errCh, context.Canceled)
++		assert.Equal(t, []string{"Delete"}, ep.actionEvents)
++	})
+ }
+ 
+ type fakeExecProcess struct {
+-- 
+2.35.6
+
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index af756ba8..2b037110 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -16,6 +16,7 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://CVE-2026-50195.patch \
            file://CVE-2026-53492.patch \
            file://CVE-2026-53489.patch \
+           file://CVE-2026-53495.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* [meta-virtualization][wrynose][PATCH 5/5] containerd: Fix CVE-2026-53493
  2026-10-16 10:06 [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
                   ` (2 preceding siblings ...)
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 4/5] containerd: Fix CVE-2026-53495 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-16 10:06 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
  2026-10-22 19:09   ` Bruce Ashfield
  2026-10-22 19:09 ` [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Bruce Ashfield
  4 siblings, 1 reply; 10+ messages in thread
From: Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 10:06 UTC (permalink / raw)
  To: meta-virtualization

From: Deepak Rathore <deeratho@cisco.com>

This patch applies the upstream release/2.2 backport for
CVE-2026-53493. It bounds Dispatch concurrency and references, then
bounds Walk references during image pull processing to prevent
uncontrolled descriptor-graph traversal and resource exhaustion.

The individual upstream backport commits are referenced in [1] and [2].
The public CVE advisory is referenced in [3].

[1] https://github.com/containerd/containerd/commit/efd11fc9c2935f1a7ce6626b348d1461bac34acf
[2] https://github.com/containerd/containerd/commit/053c0cc412f2a36c77a973d0b62ff4fa4815a9bb
[3] https://github.com/containerd/containerd/security/advisories/GHSA-pg57-6jwg-q645

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
 .../containerd/CVE-2026-53493_p1.patch        | 646 ++++++++++++++++++
 .../containerd/CVE-2026-53493_p2.patch        | 329 +++++++++
 .../containerd/containerd_git.bb              |   2 +
 3 files changed, 977 insertions(+)
 create mode 100644 recipes-containers/containerd/containerd/CVE-2026-53493_p1.patch
 create mode 100644 recipes-containers/containerd/containerd/CVE-2026-53493_p2.patch

diff --git a/recipes-containers/containerd/containerd/CVE-2026-53493_p1.patch b/recipes-containers/containerd/containerd/CVE-2026-53493_p1.patch
new file mode 100644
index 00000000..b4fd6ded
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-53493_p1.patch
@@ -0,0 +1,646 @@
+From 95d5ef2d8204a639c8f452d8457b82406802a540 Mon Sep 17 00:00:00 2001
+From: Chris Henzie <chrishenzie@gmail.com>
+Date: Tue, 28 Jul 2026 14:09:57 -0700
+Subject: [PATCH 1/2] Bound Dispatch concurrency and references
+
+Traverse breadth-first, one level at a time. Acquire the limiter before
+starting each handler goroutine. Default to 32 concurrent handlers when
+no limiter is provided. Children wait until every handler in the current
+level finishes.
+
+Preserve duplicate visits and return an error wrapping
+ErrResourceExhausted when the traversal exceeds 10,000 references.
+Count children as handlers return them to bound the descriptors retained
+for the next level.
+
+CVE: CVE-2026-53493
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/efd11fc9c2935f1a7ce6626b348d1461bac34acf]
+
+Assisted-by: Claude Code
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit 94e83c14c8aac963e24e28105dab9c9af812a2a8)
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit efd11fc9c2935f1a7ce6626b348d1461bac34acf)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ core/images/handlers.go      | 101 ++++++--
+ core/images/handlers_test.go | 469 +++++++++++++++++++++++++++++++++++
+ 2 files changed, 543 insertions(+), 27 deletions(-)
+ create mode 100644 core/images/handlers_test.go
+
+diff --git a/core/images/handlers.go b/core/images/handlers.go
+index 245b9ac43..a60835f97 100644
+--- a/core/images/handlers.go
++++ b/core/images/handlers.go
+@@ -23,6 +23,7 @@ import (
+ 	"slices"
+ 	"sort"
+ 	"strings"
++	"sync"
+ 
+ 	"github.com/containerd/containerd/v2/core/content"
+ 	"github.com/containerd/errdefs"
+@@ -32,6 +33,16 @@ import (
+ 	"golang.org/x/sync/semaphore"
+ )
+ 
++const (
++	// defaultMaxConcurrency bounds the number of concurrent handler
++	// goroutines when Dispatch is called with a nil limiter.
++	defaultMaxConcurrency = 32
++
++	// maxReferences caps the references a single Dispatch admits, duplicates
++	// included.
++	maxReferences = 10_000
++)
++
+ var (
+ 	// ErrSkipDesc is used to skip processing of a descriptor and
+ 	// its descendants.
+@@ -146,45 +157,81 @@ func WalkNotEmpty(ctx context.Context, handler Handler, descs ...ocispec.Descrip
+ // handler may return `ErrSkipDesc` to signal to the dispatcher to not traverse
+ // any children.
+ //
+-// A concurrency limiter can be passed in to limit the number of concurrent
+-// handlers running. When limiter is nil, there is no limit.
++// The limiter bounds concurrent handlers. When limiter is nil,
++// the limit is 32.
+ //
+ // Typically, this function will be used with `FetchHandler`, often composed
+ // with other handlers.
+ //
+ // If any handler returns an error, the dispatch session will be canceled.
++//
++// Each call is limited to 10,000 references, including duplicates.
++// Exceeding this limit returns an error wrapping
++// [errdefs.ErrResourceExhausted].
+ func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error {
+-	eg, ctx2 := errgroup.WithContext(ctx)
+-	for _, desc := range descs {
+-		if limiter != nil {
+-			if err := limiter.Acquire(ctx, 1); err != nil {
+-				return err
+-			}
++	if len(descs) == 0 {
++		return nil
++	}
++	if err := ctx.Err(); err != nil {
++		return err
++	}
++
++	if limiter == nil {
++		limiter = semaphore.NewWeighted(defaultMaxConcurrency)
++	}
++
++	var (
++		mu             sync.Mutex
++		next           []ocispec.Descriptor
++		referenceCount int
++	)
++
++	admit := func(descs []ocispec.Descriptor) error {
++		mu.Lock()
++		defer mu.Unlock()
++		if referenceCount+len(descs) > maxReferences {
++			return fmt.Errorf("too many descriptors (limit %d): %w", maxReferences, errdefs.ErrResourceExhausted)
+ 		}
++		referenceCount += len(descs)
++		next = append(next, descs...)
++		return nil
++	}
++
++	if err := admit(descs); err != nil {
++		return err
++	}
+ 
+-		eg.Go(func() error {
+-			desc := desc
++	for len(next) > 0 {
++		level := next
++		next = nil
+ 
+-			children, err := handler.Handle(ctx2, desc)
+-			if limiter != nil {
+-				limiter.Release(1)
++		eg, egCtx := errgroup.WithContext(ctx)
++		for _, desc := range level {
++			// Acquire here to bound the number of handler goroutines.
++			if err := limiter.Acquire(egCtx, 1); err != nil {
++				break
+ 			}
+-			if err != nil {
+-				if errors.Is(err, ErrSkipDesc) {
+-					return nil // don't traverse the children.
++			eg.Go(func() error {
++				defer limiter.Release(1)
++				children, err := handler.Handle(egCtx, desc)
++				if err != nil {
++					if errors.Is(err, ErrSkipDesc) {
++						return nil // don't traverse the children.
++					}
++					return err
+ 				}
+-				return err
+-			}
+-
+-			if len(children) > 0 {
+-				return Dispatch(ctx2, handler, limiter, children...)
+-			}
+-
+-			return nil
+-		})
++				return admit(children)
++			})
++		}
++		if err := eg.Wait(); err != nil {
++			return err
++		}
++		// Acquire can fail on cancellation even if every handler returns nil.
++		if err := ctx.Err(); err != nil {
++			return err
++		}
+ 	}
+-
+-	return eg.Wait()
++	return nil
+ }
+ 
+ // ChildrenHandler decodes well-known manifest types and returns their children.
+diff --git a/core/images/handlers_test.go b/core/images/handlers_test.go
+new file mode 100644
+index 000000000..5c4e54e86
+--- /dev/null
++++ b/core/images/handlers_test.go
+@@ -0,0 +1,469 @@
++/*
++   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 images
++
++import (
++	"context"
++	"errors"
++	"fmt"
++	"sync"
++	"sync/atomic"
++	"testing"
++	"time"
++
++	"github.com/containerd/errdefs"
++	digest "github.com/opencontainers/go-digest"
++	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
++	"github.com/stretchr/testify/assert"
++	"github.com/stretchr/testify/require"
++	"golang.org/x/sync/errgroup"
++	"golang.org/x/sync/semaphore"
++)
++
++func newDesc(id string) ocispec.Descriptor {
++	return ocispec.Descriptor{
++		Digest:    digest.FromString(id),
++		MediaType: ocispec.MediaTypeImageLayer,
++		Size:      1,
++	}
++}
++
++func digestSet(digests []digest.Digest) map[digest.Digest]struct{} {
++	set := make(map[digest.Digest]struct{}, len(digests))
++	for _, d := range digests {
++		set[d] = struct{}{}
++	}
++	return set
++}
++
++func TestDispatchTreeTraversal(t *testing.T) {
++	root := newDesc("root")
++	child1 := newDesc("child1")
++	child2 := newDesc("child2")
++	grandchild1 := newDesc("grandchild1")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest:   {child1, child2},
++		child1.Digest: {grandchild1},
++	}
++
++	var visited []digest.Digest
++	var mu sync.Mutex
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		mu.Lock()
++		visited = append(visited, desc.Digest)
++		mu.Unlock()
++		return graph[desc.Digest], nil
++	})
++
++	err := Dispatch(context.Background(), handler, nil, root)
++	require.NoError(t, err)
++
++	set := digestSet(visited)
++	assert.Contains(t, set, root.Digest)
++	assert.Contains(t, set, child1.Digest)
++	assert.Contains(t, set, child2.Digest)
++	assert.Contains(t, set, grandchild1.Digest)
++	assert.Len(t, visited, 4)
++}
++
++func TestDispatchRepeatedReferences(t *testing.T) {
++	root := newDesc("root")
++	childA := newDesc("A")
++	childB := newDesc("B")
++	shared := newDesc("shared")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest:   {childA, childB},
++		childA.Digest: {shared},
++		childB.Digest: {shared},
++	}
++
++	tests := []struct {
++		name            string
++		dispatch        func(context.Context, Handler, *semaphore.Weighted, ...ocispec.Descriptor) error
++		wantTotalCalls  int32
++		wantSharedCalls int32
++	}{
++		{
++			name:            "Dispatch visits every reference",
++			dispatch:        Dispatch,
++			wantTotalCalls:  5,
++			wantSharedCalls: 2,
++		},
++	}
++
++	for _, tt := range tests {
++		t.Run(tt.name, func(t *testing.T) {
++			var totalCalls atomic.Int32
++			var sharedCalls atomic.Int32
++			handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++				totalCalls.Add(1)
++				if desc.Digest == shared.Digest {
++					sharedCalls.Add(1)
++				}
++				return graph[desc.Digest], nil
++			})
++
++			err := tt.dispatch(context.Background(), handler, nil, root)
++			require.NoError(t, err)
++			assert.Equal(t, tt.wantTotalCalls, totalCalls.Load())
++			assert.Equal(t, tt.wantSharedCalls, sharedCalls.Load())
++		})
++	}
++}
++
++func TestDispatchErrSkipDesc(t *testing.T) {
++	root := newDesc("root")
++	child1 := newDesc("child1")
++	child2 := newDesc("child2")
++	grandchild := newDesc("grandchild")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest:   {child1, child2},
++		child1.Digest: {grandchild},
++	}
++
++	var visited []digest.Digest
++	var mu sync.Mutex
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		mu.Lock()
++		visited = append(visited, desc.Digest)
++		mu.Unlock()
++		if desc.Digest == child1.Digest {
++			return graph[desc.Digest], ErrSkipDesc
++		}
++		return graph[desc.Digest], nil
++	})
++
++	err := Dispatch(context.Background(), handler, nil, root)
++	require.NoError(t, err)
++
++	set := digestSet(visited)
++	assert.Contains(t, set, root.Digest)
++	assert.Contains(t, set, child1.Digest)
++	assert.Contains(t, set, child2.Digest)
++	assert.NotContains(t, set, grandchild.Digest, "grandchild must not be visited when its parent returns ErrSkipDesc")
++}
++
++func TestDispatchConcurrencyLimit(t *testing.T) {
++	tests := []struct {
++		name           string
++		limiter        *semaphore.Weighted
++		maxConcurrency int32
++		numChildren    int
++	}{
++		{
++			name:           "With explicit limiter",
++			limiter:        semaphore.NewWeighted(3),
++			maxConcurrency: 3,
++			numChildren:    3 * 5,
++		},
++		{
++			name:           "With default limiter",
++			limiter:        nil,
++			maxConcurrency: defaultMaxConcurrency,
++			numChildren:    defaultMaxConcurrency * 5,
++		},
++	}
++
++	for _, tt := range tests {
++		t.Run(tt.name, func(t *testing.T) {
++			root := newDesc("root")
++			children := make([]ocispec.Descriptor, tt.numChildren)
++			for i := range tt.numChildren {
++				children[i] = newDesc(fmt.Sprintf("child-%d", i))
++			}
++
++			graph := map[digest.Digest][]ocispec.Descriptor{
++				root.Digest: children,
++			}
++
++			var concurrent, peak atomic.Int32
++			var exceeded atomic.Bool
++
++			handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++				n := concurrent.Add(1)
++				if n > tt.maxConcurrency {
++					exceeded.Store(true)
++				}
++				for {
++					old := peak.Load()
++					if n <= old || peak.CompareAndSwap(old, n) {
++						break
++					}
++				}
++				defer concurrent.Add(-1)
++
++				// Keep handlers running long enough to overlap.
++				time.Sleep(10 * time.Millisecond)
++				return graph[desc.Digest], nil
++			})
++
++			err := Dispatch(context.Background(), handler, tt.limiter, root)
++			require.NoError(t, err)
++
++			assert.False(t, exceeded.Load(), "concurrent handlers must not exceed capacity")
++			assert.Equal(t, tt.maxConcurrency, peak.Load(),
++				"concurrent handlers must reach the capacity the caller asked for")
++		})
++	}
++}
++
++func TestDispatchSharedLimiter(t *testing.T) {
++	const (
++		weight  = 4
++		callers = 4
++		perTree = 12
++	)
++
++	limiter := semaphore.NewWeighted(weight)
++
++	var concurrent atomic.Int32
++	var exceeded atomic.Bool
++	visited := make([]atomic.Int32, callers)
++
++	var eg errgroup.Group
++	for c := range callers {
++		eg.Go(func() error {
++			root := newDesc(fmt.Sprintf("root-%d", c))
++			children := make([]ocispec.Descriptor, perTree)
++			for i := range perTree {
++				children[i] = newDesc(fmt.Sprintf("caller-%d-child-%d", c, i))
++			}
++			graph := map[digest.Digest][]ocispec.Descriptor{root.Digest: children}
++
++			handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++				if concurrent.Add(1) > weight {
++					exceeded.Store(true)
++				}
++				defer concurrent.Add(-1)
++				visited[c].Add(1)
++
++				time.Sleep(2 * time.Millisecond)
++				return graph[desc.Digest], nil
++			})
++
++			return Dispatch(context.Background(), handler, limiter, root)
++		})
++	}
++
++	done := make(chan error, 1)
++	go func() { done <- eg.Wait() }()
++
++	select {
++	case err := <-done:
++		require.NoError(t, err)
++	case <-time.After(30 * time.Second):
++		t.Fatal("shared-limiter traversals did not complete; a caller is starved or deadlocked")
++	}
++
++	assert.False(t, exceeded.Load(), "combined concurrent handlers must not exceed the shared limiter weight")
++	for c := range callers {
++		assert.Equal(t, int32(perTree+1), visited[c].Load(),
++			"caller %d must visit every descriptor in its own graph", c)
++	}
++}
++
++func TestDispatchReferenceLimit(t *testing.T) {
++	// Each root is below the cap on its own; their combined references exceed it.
++	rootA := newDesc("root-a")
++	rootB := newDesc("root-b")
++
++	childrenA := make([]ocispec.Descriptor, maxReferences/2)
++	childrenB := make([]ocispec.Descriptor, maxReferences/2)
++	for i := range childrenA {
++		childrenA[i] = newDesc(fmt.Sprintf("a-%d", i))
++		childrenB[i] = newDesc(fmt.Sprintf("b-%d", i))
++	}
++
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		switch desc.Digest {
++		case rootA.Digest:
++			return childrenA, nil
++		case rootB.Digest:
++			return childrenB, nil
++		}
++		return nil, nil
++	})
++
++	require.ErrorIs(t, Dispatch(context.Background(), handler, nil, rootA, rootB), errdefs.ErrResourceExhausted)
++
++	acceptRoot := newDesc("accept-root")
++	exact := make([]ocispec.Descriptor, maxReferences-1)
++	for i := range exact {
++		exact[i] = newDesc(fmt.Sprintf("accept-%d", i))
++	}
++	acceptHandler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == acceptRoot.Digest {
++			return exact, nil
++		}
++		return nil, nil
++	})
++	require.NoError(t, Dispatch(context.Background(), acceptHandler, nil, acceptRoot))
++}
++
++func TestDispatchRejectsChildrenBeforeVisiting(t *testing.T) {
++	root := newDesc("root")
++	children := make([]ocispec.Descriptor, maxReferences)
++	for i := range children {
++		children[i] = newDesc(fmt.Sprintf("node-%d", i))
++	}
++	var childVisits atomic.Int32
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == root.Digest {
++			return children, nil
++		}
++		childVisits.Add(1)
++		return nil, nil
++	})
++
++	require.ErrorIs(t, Dispatch(context.Background(), handler, nil, root), errdefs.ErrResourceExhausted)
++	assert.Equal(t, int32(0), childVisits.Load(), "children of an over-limit slice must not be visited")
++}
++
++func TestDispatchDuplicateRoots(t *testing.T) {
++	root := newDesc("root")
++
++	tests := []struct {
++		name      string
++		dispatch  func(context.Context, Handler, *semaphore.Weighted, ...ocispec.Descriptor) error
++		wantCalls int32
++	}{
++		{"Dispatch visits each root", Dispatch, 2},
++	}
++
++	for _, tt := range tests {
++		t.Run(tt.name, func(t *testing.T) {
++			var calls atomic.Int32
++			handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++				calls.Add(1)
++				return nil, nil
++			})
++			require.NoError(t, tt.dispatch(context.Background(), handler, nil, root, root))
++			assert.Equal(t, tt.wantCalls, calls.Load())
++		})
++	}
++}
++
++func TestDispatchCountsDuplicateReferences(t *testing.T) {
++	root := newDesc("root")
++	dup := newDesc("dup")
++	children := make([]ocispec.Descriptor, maxReferences+1)
++	for i := range children {
++		children[i] = dup
++	}
++	fanout := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == root.Digest {
++			return children, nil
++		}
++		return nil, nil
++	})
++	require.ErrorIs(t, Dispatch(context.Background(), fanout, nil, root), errdefs.ErrResourceExhausted)
++
++	a, b := newDesc("a"), newDesc("b")
++	cycle := map[digest.Digest][]ocispec.Descriptor{a.Digest: {b}, b.Digest: {a}}
++	cyclic := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		return cycle[desc.Digest], nil
++	})
++	require.ErrorIs(t, Dispatch(context.Background(), cyclic, nil, a), errdefs.ErrResourceExhausted)
++}
++
++func TestDispatchErrorPropagation(t *testing.T) {
++	root := newDesc("root")
++	child1 := newDesc("child1")
++	child2 := newDesc("child2")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest: {child1, child2},
++	}
++
++	errTest := errors.New("handler error")
++
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == child1.Digest {
++			return nil, errTest
++		}
++		return graph[desc.Digest], nil
++	})
++
++	err := Dispatch(context.Background(), handler, nil, root)
++	require.ErrorIs(t, err, errTest)
++}
++
++func TestDispatchContextCancellation(t *testing.T) {
++	ctx, cancel := context.WithCancel(context.Background())
++	cancel()
++
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		return nil, nil
++	})
++
++	err := Dispatch(ctx, handler, nil, newDesc("root"))
++	require.ErrorIs(t, err, context.Canceled)
++}
++
++func TestDispatchContextCancellationMidFlight(t *testing.T) {
++	// Use more descriptors than the limiter admits so cancellation also covers
++	// handlers waiting to start.
++	const n = defaultMaxConcurrency * 4
++	descs := make([]ocispec.Descriptor, n)
++	for i := range n {
++		descs[i] = newDesc(fmt.Sprintf("cancel-%d", i))
++	}
++
++	ctx, cancel := context.WithCancel(context.Background())
++	defer cancel()
++
++	started := make(chan struct{}, n)
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		started <- struct{}{}
++		<-ctx.Done()
++		return nil, ctx.Err()
++	})
++
++	go func() {
++		for range defaultMaxConcurrency {
++			<-started
++		}
++		cancel()
++	}()
++
++	err := Dispatch(ctx, handler, nil, descs...)
++	require.ErrorIs(t, err, context.Canceled)
++}
++
++func TestDispatchCancellationWithNoHandlerError(t *testing.T) {
++	const n = defaultMaxConcurrency * 4
++	descs := make([]ocispec.Descriptor, n)
++	for i := range n {
++		descs[i] = newDesc(fmt.Sprintf("cancel-queued-%d", i))
++	}
++
++	ctx, cancel := context.WithCancel(context.Background())
++	defer cancel()
++
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == descs[0].Digest {
++			cancel()
++		}
++		return nil, nil
++	})
++
++	err := Dispatch(ctx, handler, nil, descs...)
++	require.ErrorIs(t, err, context.Canceled)
++}
+-- 
+2.53.0
+
diff --git a/recipes-containers/containerd/containerd/CVE-2026-53493_p2.patch b/recipes-containers/containerd/containerd/CVE-2026-53493_p2.patch
new file mode 100644
index 00000000..d76cfb2e
--- /dev/null
+++ b/recipes-containers/containerd/containerd/CVE-2026-53493_p2.patch
@@ -0,0 +1,329 @@
+From 77f0eab284656ca3b609ad347a83bb6adb9d6833 Mon Sep 17 00:00:00 2001
+From: Chris Henzie <chrishenzie@gmail.com>
+Date: Tue, 28 Jul 2026 14:18:32 -0700
+Subject: [PATCH 2/2] Bound Walk references
+
+Return an error wrapping ErrResourceExhausted when the walk exceeds
+10,000 references. Preserve duplicate visits and count every reference
+toward the limit across recursive calls and roots.
+
+CVE: CVE-2026-53493
+Upstream-Status: Backport [https://github.com/containerd/containerd/commit/053c0cc412f2a36c77a973d0b62ff4fa4815a9bb]
+
+Assisted-by: Claude Code
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit 4f5f32636d47f051751065cf824a10da70c619fe)
+Signed-off-by: Chris Henzie <chrishenzie@gmail.com>
+(cherry picked from commit 053c0cc412f2a36c77a973d0b62ff4fa4815a9bb)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ core/images/handlers.go      |  25 +++-
+ core/images/handlers_test.go | 240 +++++++++++++++++++++++++++++++++++
+ 2 files changed, 261 insertions(+), 4 deletions(-)
+
+diff --git a/core/images/handlers.go b/core/images/handlers.go
+index a60835f97..7d1ec35cb 100644
+--- a/core/images/handlers.go
++++ b/core/images/handlers.go
+@@ -38,8 +38,8 @@ const (
+ 	// goroutines when Dispatch is called with a nil limiter.
+ 	defaultMaxConcurrency = 32
+ 
+-	// maxReferences caps the references a single Dispatch admits, duplicates
+-	// included.
++	// maxReferences caps the references in a single Dispatch or Walk.
++	// Duplicate references count toward this limit.
+ 	maxReferences = 10_000
+ )
+ 
+@@ -99,9 +99,26 @@ func Handlers(handlers ...Handler) HandlerFunc {
+ //
+ // This differs from dispatch in that each sibling resource is considered
+ // synchronously.
++//
++// Each call is limited to 10,000 references, including duplicates.
++// Exceeding this limit returns an error wrapping
++// [errdefs.ErrResourceExhausted].
+ func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
+-	for _, desc := range descs {
++	w := &walker{}
++	return w.walk(ctx, handler, descs...)
++}
+ 
++type walker struct {
++	referenceCount int
++}
++
++func (w *walker) walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
++	if w.referenceCount+len(descs) > maxReferences {
++		return fmt.Errorf("too many descriptors (limit %d): %w", maxReferences, errdefs.ErrResourceExhausted)
++	}
++	w.referenceCount += len(descs)
++
++	for _, desc := range descs {
+ 		children, err := handler.Handle(ctx, desc)
+ 		if err != nil {
+ 			if errors.Is(err, ErrSkipDesc) {
+@@ -111,7 +128,7 @@ func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) err
+ 		}
+ 
+ 		if len(children) > 0 {
+-			if err := Walk(ctx, handler, children...); err != nil {
++			if err := w.walk(ctx, handler, children...); err != nil {
+ 				return err
+ 			}
+ 		}
+diff --git a/core/images/handlers_test.go b/core/images/handlers_test.go
+index 5c4e54e86..acad816d7 100644
+--- a/core/images/handlers_test.go
++++ b/core/images/handlers_test.go
+@@ -50,6 +50,246 @@ func digestSet(digests []digest.Digest) map[digest.Digest]struct{} {
+ 	return set
+ }
+ 
++func TestWalkTreeTraversal(t *testing.T) {
++	root := newDesc("root")
++	child1 := newDesc("child1")
++	child2 := newDesc("child2")
++	grandchild1 := newDesc("grandchild1")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest:   {child1, child2},
++		child1.Digest: {grandchild1},
++	}
++
++	var visited []digest.Digest
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		visited = append(visited, desc.Digest)
++		return graph[desc.Digest], nil
++	})
++
++	err := Walk(context.Background(), handler, root)
++	require.NoError(t, err)
++
++	want := []digest.Digest{root.Digest, child1.Digest, grandchild1.Digest, child2.Digest}
++	assert.Equal(t, want, visited)
++}
++
++func TestWalkRepeatedReferences(t *testing.T) {
++	root := newDesc("root")
++	childA := newDesc("A")
++	childB := newDesc("B")
++	shared := newDesc("shared")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest:   {childA, childB},
++		childA.Digest: {shared},
++		childB.Digest: {shared},
++	}
++
++	tests := []struct {
++		name            string
++		walk            func(context.Context, Handler, ...ocispec.Descriptor) error
++		wantTotalCalls  int
++		wantSharedCalls int
++	}{
++		{
++			name:            "Walk visits every reference",
++			walk:            Walk,
++			wantTotalCalls:  5,
++			wantSharedCalls: 2,
++		},
++	}
++
++	for _, tt := range tests {
++		t.Run(tt.name, func(t *testing.T) {
++			var totalCalls, sharedCalls int
++			handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++				totalCalls++
++				if desc.Digest == shared.Digest {
++					sharedCalls++
++				}
++				return graph[desc.Digest], nil
++			})
++
++			err := tt.walk(context.Background(), handler, root)
++			require.NoError(t, err)
++			assert.Equal(t, tt.wantTotalCalls, totalCalls)
++			assert.Equal(t, tt.wantSharedCalls, sharedCalls)
++		})
++	}
++}
++
++func TestWalkErrSkipDesc(t *testing.T) {
++	root := newDesc("root")
++	child1 := newDesc("child1")
++	child2 := newDesc("child2")
++	grandchild := newDesc("grandchild")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest:   {child1, child2},
++		child1.Digest: {grandchild},
++	}
++
++	var visited []digest.Digest
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		visited = append(visited, desc.Digest)
++		if desc.Digest == child1.Digest {
++			return graph[desc.Digest], ErrSkipDesc
++		}
++		return graph[desc.Digest], nil
++	})
++
++	err := Walk(context.Background(), handler, root)
++	require.NoError(t, err)
++
++	set := digestSet(visited)
++	assert.Contains(t, set, root.Digest)
++	assert.Contains(t, set, child1.Digest)
++	assert.Contains(t, set, child2.Digest)
++	assert.NotContains(t, set, grandchild.Digest, "grandchild must not be visited when its parent returns ErrSkipDesc")
++}
++
++func TestWalkReferenceLimit(t *testing.T) {
++	root := newDesc("root")
++	handlerReturning := func(childCount int) Handler {
++		children := make([]ocispec.Descriptor, childCount)
++		for i := range children {
++			children[i] = newDesc(fmt.Sprintf("node-%d", i))
++		}
++		return HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++			if desc.Digest == root.Digest {
++				return children, nil
++			}
++			return nil, nil
++		})
++	}
++
++	require.NoError(t, Walk(context.Background(), handlerReturning(maxReferences-1), root))
++	require.ErrorIs(t, Walk(context.Background(), handlerReturning(maxReferences), root), errdefs.ErrResourceExhausted)
++}
++
++func TestWalkRejectsChildrenBeforeVisiting(t *testing.T) {
++	root := newDesc("root")
++	children := make([]ocispec.Descriptor, maxReferences)
++	for i := range children {
++		children[i] = newDesc(fmt.Sprintf("node-%d", i))
++	}
++	var childVisits int
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == root.Digest {
++			return children, nil
++		}
++		childVisits++
++		return nil, nil
++	})
++
++	require.ErrorIs(t, Walk(context.Background(), handler, root), errdefs.ErrResourceExhausted)
++	assert.Equal(t, 0, childVisits, "children of an over-limit slice must not be visited")
++}
++
++func TestWalkErrorPropagation(t *testing.T) {
++	root := newDesc("root")
++	child1 := newDesc("child1")
++	child2 := newDesc("child2")
++
++	graph := map[digest.Digest][]ocispec.Descriptor{
++		root.Digest: {child1, child2},
++	}
++
++	errTest := errors.New("handler error")
++
++	handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == child1.Digest {
++			return nil, errTest
++		}
++		return graph[desc.Digest], nil
++	})
++
++	err := Walk(context.Background(), handler, root)
++	require.ErrorIs(t, err, errTest)
++}
++
++func TestWalkDuplicateRoots(t *testing.T) {
++	root := newDesc("root")
++
++	tests := []struct {
++		name      string
++		walk      func(context.Context, Handler, ...ocispec.Descriptor) error
++		wantCalls int
++	}{
++		{"Walk visits each root", Walk, 2},
++	}
++
++	for _, tt := range tests {
++		t.Run(tt.name, func(t *testing.T) {
++			var calls int
++			handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++				calls++
++				return nil, nil
++			})
++			require.NoError(t, tt.walk(context.Background(), handler, root, root))
++			assert.Equal(t, tt.wantCalls, calls)
++		})
++	}
++}
++
++func TestWalkCountsDuplicateReferences(t *testing.T) {
++	root := newDesc("root")
++	dup := newDesc("dup")
++	children := make([]ocispec.Descriptor, maxReferences+1)
++	for i := range children {
++		children[i] = dup
++	}
++	fanout := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		if desc.Digest == root.Digest {
++			return children, nil
++		}
++		return nil, nil
++	})
++	require.ErrorIs(t, Walk(context.Background(), fanout, root), errdefs.ErrResourceExhausted)
++
++	a, b := newDesc("a"), newDesc("b")
++	cycle := map[digest.Digest][]ocispec.Descriptor{a.Digest: {b}, b.Digest: {a}}
++	cyclic := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++		return cycle[desc.Digest], nil
++	})
++	require.ErrorIs(t, Walk(context.Background(), cyclic, a), errdefs.ErrResourceExhausted)
++}
++
++func TestWalkNotEmpty(t *testing.T) {
++	root := newDesc("root")
++	child := newDesc("child")
++
++	t.Run("returns nil when a handler produces children", func(t *testing.T) {
++		graph := map[digest.Digest][]ocispec.Descriptor{root.Digest: {child}}
++		handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++			return graph[desc.Digest], nil
++		})
++		require.NoError(t, WalkNotEmpty(context.Background(), handler, root))
++	})
++
++	t.Run("returns ErrEmptyWalk when everything is filtered out", func(t *testing.T) {
++		handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++			return nil, nil
++		})
++		require.ErrorIs(t, WalkNotEmpty(context.Background(), handler, root), ErrEmptyWalk)
++	})
++
++	t.Run("propagates the reference-cap error", func(t *testing.T) {
++		children := make([]ocispec.Descriptor, maxReferences+1)
++		for i := range children {
++			children[i] = newDesc(fmt.Sprintf("node-%d", i))
++		}
++		handler := HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
++			if desc.Digest == root.Digest {
++				return children, nil
++			}
++			return nil, nil
++		})
++		require.ErrorIs(t, WalkNotEmpty(context.Background(), handler, root), errdefs.ErrResourceExhausted)
++	})
++}
++
+ func TestDispatchTreeTraversal(t *testing.T) {
+ 	root := newDesc("root")
+ 	child1 := newDesc("child1")
+-- 
+2.53.0
+
diff --git a/recipes-containers/containerd/containerd_git.bb b/recipes-containers/containerd/containerd_git.bb
index 2b037110..48d41dbc 100644
--- a/recipes-containers/containerd/containerd_git.bb
+++ b/recipes-containers/containerd/containerd_git.bb
@@ -17,6 +17,8 @@ SRC_URI = "git://github.com/containerd/containerd;branch=release/2.2;protocol=ht
            file://CVE-2026-53492.patch \
            file://CVE-2026-53489.patch \
            file://CVE-2026-53495.patch \
+           file://CVE-2026-53493_p1.patch \
+           file://CVE-2026-53493_p2.patch \
           "
 
 # Apache-2.0 for containerd
-- 
2.35.6



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

* Re: [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195
  2026-10-16 10:06 [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
                   ` (3 preceding siblings ...)
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 5/5] containerd: Fix CVE-2026-53493 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-22 19:09 ` Bruce Ashfield
  4 siblings, 0 replies; 10+ messages in thread
From: Bruce Ashfield @ 2026-10-22 19:09 UTC (permalink / raw)
  To: meta-virtualization; +Cc: deeratho

merged

Bruce

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

* Re: [meta-virtualization][wrynose][PATCH 2/5] containerd: Fix CVE-2026-53492
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 2/5] containerd: Fix CVE-2026-53492 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-22 19:09   ` Bruce Ashfield
  0 siblings, 0 replies; 10+ messages in thread
From: Bruce Ashfield @ 2026-10-22 19:09 UTC (permalink / raw)
  To: meta-virtualization; +Cc: deeratho

merged

Bruce

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

* Re: [meta-virtualization][wrynose][PATCH 3/5] containerd: Fix CVE-2026-53489
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 3/5] containerd: Fix CVE-2026-53489 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-22 19:09   ` Bruce Ashfield
  0 siblings, 0 replies; 10+ messages in thread
From: Bruce Ashfield @ 2026-10-22 19:09 UTC (permalink / raw)
  To: meta-virtualization; +Cc: deeratho

merged

Bruce

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

* Re: [meta-virtualization][wrynose][PATCH 4/5] containerd: Fix CVE-2026-53495
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 4/5] containerd: Fix CVE-2026-53495 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-22 19:09   ` Bruce Ashfield
  0 siblings, 0 replies; 10+ messages in thread
From: Bruce Ashfield @ 2026-10-22 19:09 UTC (permalink / raw)
  To: meta-virtualization; +Cc: deeratho

merged

Bruce

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

* Re: [meta-virtualization][wrynose][PATCH 5/5] containerd: Fix CVE-2026-53493
  2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 5/5] containerd: Fix CVE-2026-53493 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-22 19:09   ` Bruce Ashfield
  0 siblings, 0 replies; 10+ messages in thread
From: Bruce Ashfield @ 2026-10-22 19:09 UTC (permalink / raw)
  To: meta-virtualization; +Cc: deeratho

merged

Bruce

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

end of thread, other threads:[~2026-10-22 19:09 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 10:06 [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 2/5] containerd: Fix CVE-2026-53492 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-22 19:09   ` Bruce Ashfield
2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 3/5] containerd: Fix CVE-2026-53489 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-22 19:09   ` Bruce Ashfield
2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 4/5] containerd: Fix CVE-2026-53495 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-22 19:09   ` Bruce Ashfield
2026-10-16 10:06 ` [meta-virtualization][wrynose][PATCH 5/5] containerd: Fix CVE-2026-53493 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-22 19:09   ` Bruce Ashfield
2026-10-22 19:09 ` [meta-virtualization][wrynose][PATCH 1/5] containerd: Fix CVE-2026-50195 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.