[meta-virtualization][wrynose][PATCH 3/3] containerd: fix CVE-2026-53488
"Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)" <[email protected]> Fri, 10 Jul 2026 17:56:52 +0530
| Newsgroups | org.yoctoproject.lists.meta-virtualization |
|---|---|
| Message-ID | <[email protected]> |
From: Deepak Rathore <[email protected]> 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 <[email protected]> --- .../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 <[email protected]> +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 <[email protected]> +Signed-off-by: Samuel Karp <[email protected]> +(cherry picked from commit 0ec1af4cae1256d18719ca892bf66340499e8050) +Signed-off-by: Akihiro Suda <[email protected]> +Signed-off-by: Samuel Karp <[email protected]> +(cherry picked from commit b6072a49f8d3f6efc5ac9895efbb1852b16a2602) +Signed-off-by: Deepak Rathore <[email protected]> +--- + 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