diff --git a/SPECS/containerd2/CVE-2024-25621.patch b/SPECS/containerd2/CVE-2024-25621.patch deleted file mode 100644 index d07a78a1297..00000000000 --- a/SPECS/containerd2/CVE-2024-25621.patch +++ /dev/null @@ -1,111 +0,0 @@ -From 46223b256bfb3f42e193d947d1b1ef551260749f Mon Sep 17 00:00:00 2001 -From: Akihiro Suda -Date: Mon, 27 Oct 2025 16:42:59 +0900 -Subject: [PATCH] Fix directory permissions - -- Create /var/lib/containerd with 0o700 (was: 0o711). -- Create config.TempDir with 0o700 (was: 0o711). -- Create /run/containerd/io.containerd.grpc.v1.cri with 0o700 (was: 0o755). -- Create /run/containerd/io.containerd.sandbox.controller.v1.shim with 0o700 (was: 0o711). -- Leave /run/containerd and /run/containerd/io.containerd.runtime.v2.task created with 0o711, - as required by userns-remapped containers. - /run/containerd/io.containerd.runtime.v2.task// is created with: - - 0o700 for non-userns-remapped containers - - 0o710 for userns-remapped containers with the remapped root group as the owner group. - -Signed-off-by: Akihiro Suda -Signed-off-by: Azure Linux Security Servicing Account -Upstream-reference: https://github.com/containerd/containerd/commit/7c59e8e9e970d38061a77b586b23655c352bfec5.patch ---- - cmd/containerd/server/server.go | 14 ++++++++++++-- - core/runtime/v2/task_manager.go | 2 ++ - plugins/cri/runtime/plugin.go | 7 +++++++ - plugins/sandbox/controller.go | 6 +++++- - 4 files changed, 26 insertions(+), 3 deletions(-) - -diff --git a/cmd/containerd/server/server.go b/cmd/containerd/server/server.go -index 9f38cb3..c9e3698 100644 ---- a/cmd/containerd/server/server.go -+++ b/cmd/containerd/server/server.go -@@ -81,10 +81,16 @@ func CreateTopLevelDirectories(config *srvconfig.Config) error { - return errors.New("root and state must be different paths") - } - -- if err := sys.MkdirAllWithACL(config.Root, 0o711); err != nil { -+ if err := sys.MkdirAllWithACL(config.Root, 0o700); err != nil { -+ return err -+ } -+ // chmod is needed for upgrading from an older release that created the dir with 0o711 -+ if err := os.Chmod(config.Root, 0o700); err != nil { - return err - } - -+ // For supporting userns-remapped containers, the state dir cannot be just mkdired with 0o700. -+ // Each of plugins creates a dedicated directory beneath the state dir with appropriate permission bits. - if err := sys.MkdirAllWithACL(config.State, 0o711); err != nil { - return err - } -@@ -99,7 +105,11 @@ func CreateTopLevelDirectories(config *srvconfig.Config) error { - } - - if config.TempDir != "" { -- if err := sys.MkdirAllWithACL(config.TempDir, 0o711); err != nil { -+ if err := sys.MkdirAllWithACL(config.TempDir, 0o700); err != nil { -+ return err -+ } -+ // chmod is needed for upgrading from an older release that created the dir with 0o711 -+ if err := os.Chmod(config.Root, 0o700); err != nil { - return err - } - if runtime.GOOS == "windows" { -diff --git a/core/runtime/v2/task_manager.go b/core/runtime/v2/task_manager.go -index f396ced..024763a 100644 ---- a/core/runtime/v2/task_manager.go -+++ b/core/runtime/v2/task_manager.go -@@ -74,6 +74,8 @@ func init() { - shimManager := shimManagerI.(*ShimManager) - root, state := ic.Properties[plugins.PropertyRootDir], ic.Properties[plugins.PropertyStateDir] - for _, d := range []string{root, state} { -+ // root: the parent of this directory is created as 0o700, not 0o711. -+ // state: the parent of this directory is created as 0o711 too, so as to support userns-remapped containers. - if err := os.MkdirAll(d, 0711); err != nil { - return nil, err - } -diff --git a/plugins/cri/runtime/plugin.go b/plugins/cri/runtime/plugin.go -index adc64d9..07f64a1 100644 ---- a/plugins/cri/runtime/plugin.go -+++ b/plugins/cri/runtime/plugin.go -@@ -91,6 +91,13 @@ func initCRIRuntime(ic *plugin.InitContext) (interface{}, error) { - rootDir := filepath.Join(containerdRootDir, "io.containerd.grpc.v1.cri") - containerdStateDir := filepath.Dir(ic.Properties[plugins.PropertyStateDir]) - stateDir := filepath.Join(containerdStateDir, "io.containerd.grpc.v1.cri") -+ if err := os.MkdirAll(stateDir, 0o700); err != nil { -+ return nil, err -+ } -+ // chmod is needed for upgrading from an older release that created the dir with 0o755 -+ if err := os.Chmod(stateDir, 0o700); err != nil { -+ return nil, err -+ } - c := criconfig.Config{ - RuntimeConfig: *pluginConfig, - ContainerdRootDir: containerdRootDir, -diff --git a/plugins/sandbox/controller.go b/plugins/sandbox/controller.go -index aec9cc3..165f2e8 100644 ---- a/plugins/sandbox/controller.go -+++ b/plugins/sandbox/controller.go -@@ -68,7 +68,11 @@ func init() { - state := ic.Properties[plugins.PropertyStateDir] - root := ic.Properties[plugins.PropertyRootDir] - for _, d := range []string{root, state} { -- if err := os.MkdirAll(d, 0711); err != nil { -+ if err := os.MkdirAll(d, 0700); err != nil { -+ return nil, err -+ } -+ // chmod is needed for upgrading from an older release that created the dir with 0o711 -+ if err := os.Chmod(d, 0o700); err != nil { - return nil, err - } - } --- -2.45.4 - diff --git a/SPECS/containerd2/CVE-2024-40635.patch b/SPECS/containerd2/CVE-2024-40635.patch deleted file mode 100644 index a7604073e8a..00000000000 --- a/SPECS/containerd2/CVE-2024-40635.patch +++ /dev/null @@ -1,174 +0,0 @@ -From 07a0b5419c408e70ed90179ea3e5825d986f80af Mon Sep 17 00:00:00 2001 -From: Craig Ingram -Date: Tue, 11 Mar 2025 14:52:44 +0000 -Subject: [PATCH] (cherry picked from commit - de1341c201ffb0effebbf51d00376181968c8779) - ---- - pkg/oci/spec_opts.go | 24 +++++++-- - pkg/oci/spec_opts_linux_test.go | 92 +++++++++++++++++++++++++++++++++ - 2 files changed, 112 insertions(+), 4 deletions(-) - -diff --git a/pkg/oci/spec_opts.go b/pkg/oci/spec_opts.go -index 3b85d764ae10..f7b298122957 100644 ---- a/pkg/oci/spec_opts.go -+++ b/pkg/oci/spec_opts.go -@@ -22,6 +22,7 @@ import ( - "encoding/json" - "errors" - "fmt" -+ "math" - "os" - "path/filepath" - "runtime" -@@ -593,6 +594,20 @@ func WithUser(userstr string) SpecOpts { - defer ensureAdditionalGids(s) - setProcess(s) - s.Process.User.AdditionalGids = nil -+ // While the Linux kernel allows the max UID to be MaxUint32 - 2, -+ // and the OCI Runtime Spec has no definition about the max UID, -+ // the runc implementation is known to require the UID to be <= MaxInt32. -+ // -+ // containerd follows runc's limitation here. -+ // -+ // In future we may relax this limitation to allow MaxUint32 - 2, -+ // or, amend the OCI Runtime Spec to codify the implementation limitation. -+ const ( -+ minUserID = 0 -+ maxUserID = math.MaxInt32 -+ minGroupID = 0 -+ maxGroupID = math.MaxInt32 -+ ) - - // For LCOW it's a bit harder to confirm that the user actually exists on the host as a rootfs isn't - // mounted on the host and shared into the guest, but rather the rootfs is constructed entirely in the -@@ -611,8 +626,8 @@ func WithUser(userstr string) SpecOpts { - switch len(parts) { - case 1: - v, err := strconv.Atoi(parts[0]) -- if err != nil { -- // if we cannot parse as a uint they try to see if it is a username -+ if err != nil || v < minUserID || v > maxUserID { -+ // if we cannot parse as an int32 then try to see if it is a username - return WithUsername(userstr)(ctx, client, c, s) - } - return WithUserID(uint32(v))(ctx, client, c, s) -@@ -623,12 +638,13 @@ func WithUser(userstr string) SpecOpts { - ) - var uid, gid uint32 - v, err := strconv.Atoi(parts[0]) -- if err != nil { -+ if err != nil || v < minUserID || v > maxUserID { - username = parts[0] - } else { - uid = uint32(v) - } -- if v, err = strconv.Atoi(parts[1]); err != nil { -+ v, err = strconv.Atoi(parts[1]) -+ if err != nil || v < minGroupID || v > maxGroupID { - groupname = parts[1] - } else { - gid = uint32(v) -diff --git a/pkg/oci/spec_opts_linux_test.go b/pkg/oci/spec_opts_linux_test.go -index 9299fa1807b6..d34af356b103 100644 ---- a/pkg/oci/spec_opts_linux_test.go -+++ b/pkg/oci/spec_opts_linux_test.go -@@ -33,6 +33,98 @@ import ( - "golang.org/x/sys/unix" - ) - -+//nolint:gosec -+func TestWithUser(t *testing.T) { -+ t.Parallel() -+ -+ expectedPasswd := `root:x:0:0:root:/root:/bin/ash -+guest:x:405:100:guest:/dev/null:/sbin/nologin -+` -+ expectedGroup := `root:x:0:root -+bin:x:1:root,bin,daemon -+daemon:x:2:root,bin,daemon -+sys:x:3:root,bin,adm -+guest:x:100:guest -+` -+ td := t.TempDir() -+ apply := fstest.Apply( -+ fstest.CreateDir("/etc", 0777), -+ fstest.CreateFile("/etc/passwd", []byte(expectedPasswd), 0777), -+ fstest.CreateFile("/etc/group", []byte(expectedGroup), 0777), -+ ) -+ if err := apply.Apply(td); err != nil { -+ t.Fatalf("failed to apply: %v", err) -+ } -+ c := containers.Container{ID: t.Name()} -+ testCases := []struct { -+ user string -+ expectedUID uint32 -+ expectedGID uint32 -+ err string -+ }{ -+ { -+ user: "0", -+ expectedUID: 0, -+ expectedGID: 0, -+ }, -+ { -+ user: "root:root", -+ expectedUID: 0, -+ expectedGID: 0, -+ }, -+ { -+ user: "guest", -+ expectedUID: 405, -+ expectedGID: 100, -+ }, -+ { -+ user: "guest:guest", -+ expectedUID: 405, -+ expectedGID: 100, -+ }, -+ { -+ user: "guest:nobody", -+ err: "no groups found", -+ }, -+ { -+ user: "405:100", -+ expectedUID: 405, -+ expectedGID: 100, -+ }, -+ { -+ user: "405:2147483648", -+ err: "no groups found", -+ }, -+ { -+ user: "-1000", -+ err: "no users found", -+ }, -+ { -+ user: "2147483648", -+ err: "no users found", -+ }, -+ } -+ for _, testCase := range testCases { -+ testCase := testCase -+ t.Run(testCase.user, func(t *testing.T) { -+ t.Parallel() -+ s := Spec{ -+ Version: specs.Version, -+ Root: &specs.Root{ -+ Path: td, -+ }, -+ Linux: &specs.Linux{}, -+ } -+ err := WithUser(testCase.user)(context.Background(), nil, &c, &s) -+ if err != nil { -+ assert.EqualError(t, err, testCase.err) -+ } -+ assert.Equal(t, testCase.expectedUID, s.Process.User.UID) -+ assert.Equal(t, testCase.expectedGID, s.Process.User.GID) -+ }) -+ } -+} -+ - //nolint:gosec - func TestWithUserID(t *testing.T) { - t.Parallel() diff --git a/SPECS/containerd2/CVE-2024-45338.patch b/SPECS/containerd2/CVE-2024-45338.patch deleted file mode 100644 index c2fb46031c5..00000000000 --- a/SPECS/containerd2/CVE-2024-45338.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 8e66b04771e35c4e4125e8c60334b34e2423effb Mon Sep 17 00:00:00 2001 -From: Roland Shoemaker -Date: Wed, 04 Dec 2024 09:35:55 -0800 -Subject: [PATCH] html: use strings.EqualFold instead of lowering ourselves - -Instead of using strings.ToLower and == to check case insensitive -equality, just use strings.EqualFold, even when the strings are only -ASCII. This prevents us unnecessarily lowering extremely long strings, -which can be a somewhat expensive operation, even if we're only -attempting to compare equality with five characters. - -Thanks to Guido Vranken for reporting this issue. - -Fixes golang/go#70906 -Fixes CVE-2024-45338 - -Change-Id: I323b919f912d60dab6a87cadfdcac3e6b54cd128 -Reviewed-on: https://go-review.googlesource.com/c/net/+/637536 -LUCI-TryBot-Result: Go LUCI -Auto-Submit: Gopher Robot -Reviewed-by: Roland Shoemaker -Reviewed-by: Tatiana Bradley ---- - vendor/golang.org/x/net/html/doctype.go | 2 +- - vendor/golang.org/x/net/html/foreign.go | 3 +-- - vendor/golang.org/x/net/html/parse.go | 4 ++-- - 3 files changed, 4 insertions(+), 5 deletions(-) - -diff --git a/vendor/golang.org/x/net/html/doctype.go b/vendor/golang.org/x/net/html/doctype.go -index c484e5a..bca3ae9 100644 ---- a/vendor/golang.org/x/net/html/doctype.go -+++ b/vendor/golang.org/x/net/html/doctype.go -@@ -87,7 +87,7 @@ func parseDoctype(s string) (n *Node, quirks bool) { - } - } - if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && -- strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { -+ strings.EqualFold(lastAttr.Val, "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") { - quirks = true - } - } -diff --git a/vendor/golang.org/x/net/html/foreign.go b/vendor/golang.org/x/net/html/foreign.go -index 9da9e9d..e8515d8 100644 ---- a/vendor/golang.org/x/net/html/foreign.go -+++ b/vendor/golang.org/x/net/html/foreign.go -@@ -40,8 +40,7 @@ func htmlIntegrationPoint(n *Node) bool { - if n.Data == "annotation-xml" { - for _, a := range n.Attr { - if a.Key == "encoding" { -- val := strings.ToLower(a.Val) -- if val == "text/html" || val == "application/xhtml+xml" { -+ if strings.EqualFold(a.Val, "text/html") || strings.EqualFold(a.Val, "application/xhtml+xml") { - return true - } - } -diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go -index 038941d..cb012d8 100644 ---- a/vendor/golang.org/x/net/html/parse.go -+++ b/vendor/golang.org/x/net/html/parse.go -@@ -1031,7 +1031,7 @@ func inBodyIM(p *parser) bool { - if p.tok.DataAtom == a.Input { - for _, t := range p.tok.Attr { - if t.Key == "type" { -- if strings.ToLower(t.Val) == "hidden" { -+ if strings.EqualFold(t.Val, "hidden") { - // Skip setting framesetOK = false - return true - } -@@ -1459,7 +1459,7 @@ func inTableIM(p *parser) bool { - return inHeadIM(p) - case a.Input: - for _, t := range p.tok.Attr { -- if t.Key == "type" && strings.ToLower(t.Val) == "hidden" { -+ if t.Key == "type" && strings.EqualFold(t.Val, "hidden") { - p.addElement() - p.oe.pop() - return true --- -2.25.1 - diff --git a/SPECS/containerd2/CVE-2025-22872.patch b/SPECS/containerd2/CVE-2025-22872.patch deleted file mode 100644 index c4c75f054fd..00000000000 --- a/SPECS/containerd2/CVE-2025-22872.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 072aace3657090fc2cd827741839d229aafd693e Mon Sep 17 00:00:00 2001 -From: Aninda -Date: Thu, 22 May 2025 10:01:10 -0400 -Subject: [PATCH] Address CVE-2025-22872 -Upstream Patch Reference: https://github.com/golang/net/commit/e1fcd82abba34df74614020343be8eb1fe85f0d9 - ---- - vendor/golang.org/x/net/html/token.go | 18 ++++++++++++++++-- - 1 file changed, 16 insertions(+), 2 deletions(-) - -diff --git a/vendor/golang.org/x/net/html/token.go b/vendor/golang.org/x/net/html/token.go -index 3c57880..6598c1f 100644 ---- a/vendor/golang.org/x/net/html/token.go -+++ b/vendor/golang.org/x/net/html/token.go -@@ -839,8 +839,22 @@ func (z *Tokenizer) readStartTag() TokenType { - if raw { - z.rawTag = strings.ToLower(string(z.buf[z.data.start:z.data.end])) - } -- // Look for a self-closing token like "
". -- if z.err == nil && z.buf[z.raw.end-2] == '/' { -+ // Look for a self-closing token (e.g.
). -+ // -+ // Originally, we did this by just checking that the last character of the -+ // tag (ignoring the closing bracket) was a solidus (/) character, but this -+ // is not always accurate. -+ // -+ // We need to be careful that we don't misinterpret a non-self-closing tag -+ // as self-closing, as can happen if the tag contains unquoted attribute -+ // values (i.e.

). -+ // -+ // To avoid this, we check that the last non-bracket character of the tag -+ // (z.raw.end-2) isn't the same character as the last non-quote character of -+ // the last attribute of the tag (z.pendingAttr[1].end-1), if the tag has -+ // attributes. -+ nAttrs := len(z.attr) -+ if z.err == nil && z.buf[z.raw.end-2] == '/' && (nAttrs == 0 || z.raw.end-2 != z.attr[nAttrs-1][1].end-1) { - return SelfClosingTagToken - } - return StartTagToken --- -2.34.1 - diff --git a/SPECS/containerd2/CVE-2025-27144.patch b/SPECS/containerd2/CVE-2025-27144.patch deleted file mode 100644 index 734158a6a89..00000000000 --- a/SPECS/containerd2/CVE-2025-27144.patch +++ /dev/null @@ -1,49 +0,0 @@ -From fa324fa38481f9d2da9109cb5983326f62ff7507 Mon Sep 17 00:00:00 2001 -From: Kanishk-Bansal -Date: Fri, 28 Feb 2025 07:45:53 +0000 -Subject: [PATCH] CVE-2025-27144 -Upstream Ref: https://github.com/go-jose/go-jose/commit/c9ed84d8f0cfadcfad817150158caca6fcbc518b - ---- - vendor/github.com/go-jose/go-jose/v4/jwe.go | 5 +++-- - vendor/github.com/go-jose/go-jose/v4/jws.go | 5 +++-- - 2 files changed, 6 insertions(+), 4 deletions(-) - -diff --git a/vendor/github.com/go-jose/go-jose/v4/jwe.go b/vendor/github.com/go-jose/go-jose/v4/jwe.go -index 89f03ee..9f1322d 100644 ---- a/vendor/github.com/go-jose/go-jose/v4/jwe.go -+++ b/vendor/github.com/go-jose/go-jose/v4/jwe.go -@@ -288,10 +288,11 @@ func ParseEncryptedCompact( - keyAlgorithms []KeyAlgorithm, - contentEncryption []ContentEncryption, - ) (*JSONWebEncryption, error) { -- parts := strings.Split(input, ".") -- if len(parts) != 5 { -+ // Five parts is four separators -+ if strings.Count(input, ".") != 4 { - return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts") - } -+ parts := strings.SplitN(input, ".", 5) - - rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0]) - if err != nil { -diff --git a/vendor/github.com/go-jose/go-jose/v4/jws.go b/vendor/github.com/go-jose/go-jose/v4/jws.go -index 3a91230..d09d8ba 100644 ---- a/vendor/github.com/go-jose/go-jose/v4/jws.go -+++ b/vendor/github.com/go-jose/go-jose/v4/jws.go -@@ -327,10 +327,11 @@ func parseSignedCompact( - payload []byte, - signatureAlgorithms []SignatureAlgorithm, - ) (*JSONWebSignature, error) { -- parts := strings.Split(input, ".") -- if len(parts) != 3 { -+ // Three parts is two separators -+ if strings.Count(input, ".") != 2 { - return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts") - } -+ parts := strings.SplitN(input, ".", 3) - - if parts[1] != "" && payload != nil { - return nil, fmt.Errorf("go-jose/go-jose: payload is not detached") --- -2.34.1 diff --git a/SPECS/containerd2/CVE-2025-47291.patch b/SPECS/containerd2/CVE-2025-47291.patch deleted file mode 100644 index 5393e181fb1..00000000000 --- a/SPECS/containerd2/CVE-2025-47291.patch +++ /dev/null @@ -1,220 +0,0 @@ -From 0bb95c53ec07aad729470844c8f0e5ab2838a8db Mon Sep 17 00:00:00 2001 -From: dj_palli -Date: Mon, 2 Jun 2025 14:45:30 +0000 -Subject: [PATCH] Address CVE-2025-47291 - -Upstream patch URL : https://github.com/containerd/containerd/commit/ec3567d6b369cde39739b41db8763a19d6f35c39 - ---- - client/container.go | 3 ++- - client/task.go | 27 ++++++++++++++++++++++ - client/task_opts.go | 27 ++++++++-------------- - client/task_opts_unix.go | 48 +++++++++++++--------------------------- - 4 files changed, 53 insertions(+), 52 deletions(-) - -diff --git a/client/container.go b/client/container.go -index b9cf25e..5763ae6 100644 ---- a/client/container.go -+++ b/client/container.go -@@ -279,7 +279,8 @@ func (c *container) NewTask(ctx context.Context, ioCreate cio.Creator, opts ...N - } - } - info := TaskInfo{ -- runtime: r.Runtime.Name, -+ runtime: r.Runtime.Name, -+ runtimeOptions: r.Runtime.Options, - } - for _, o := range opts { - if err := o(ctx, c.client, &info); err != nil { -diff --git a/client/task.go b/client/task.go -index 20312a9..152babe 100644 ---- a/client/task.go -+++ b/client/task.go -@@ -146,6 +146,10 @@ type TaskInfo struct { - - // runtime is the runtime name for the container, and cannot be changed. - runtime string -+ // runtimeOptions is the runtime options for the container, and when task options are set, -+ // they will be based on the runtimeOptions. -+ // https://github.com/containerd/containerd/issues/11568 -+ runtimeOptions typeurl.Any - } - - // Runtime name for the container -@@ -153,6 +157,29 @@ func (i *TaskInfo) Runtime() string { - return i.runtime - } - -+// getRuncOptions returns a reference to the runtime options for use by the task. -+// If the set of options is not set by the opts passed into the NewTask creation -+// this function first attempts to initialize the runtime options with a copy of the runtimeOptions, -+// otherwise an empty set of options is assigned and returned -+func (i *TaskInfo) getRuncOptions() (*options.Options, error) { -+ if i.Options != nil { -+ opts, ok := i.Options.(*options.Options) -+ if !ok { -+ return nil, errors.New("invalid runtime v2 options format") -+ } -+ return opts, nil -+ } -+ -+ opts := &options.Options{} -+ if i.runtimeOptions != nil && i.runtimeOptions.GetValue() != nil { -+ if err := typeurl.UnmarshalTo(i.runtimeOptions, opts); err != nil { -+ return nil, fmt.Errorf("failed to get runtime v2 options: %w", err) -+ } -+ } -+ i.Options = opts -+ return opts, nil -+} -+ - // Task is the executable object within containerd - type Task interface { - Process -diff --git a/client/task_opts.go b/client/task_opts.go -index 8e94d4c..27bde35 100644 ---- a/client/task_opts.go -+++ b/client/task_opts.go -@@ -54,12 +54,9 @@ func WithRuntimePath(absRuntimePath string) NewTaskOpts { - // usually it is served inside a sandbox, and we can get it from sandbox status. - func WithTaskAPIEndpoint(address string, version uint32) NewTaskOpts { - return func(ctx context.Context, client *Client, info *TaskInfo) error { -- if info.Options == nil { -- info.Options = &options.Options{} -- } -- opts, ok := info.Options.(*options.Options) -- if !ok { -- return errors.New("invalid runtime v2 options format") -+ opts, err := info.getRuncOptions() -+ if err != nil { -+ return err - } - opts.TaskApiAddress = address - opts.TaskApiVersion = version -@@ -119,12 +116,9 @@ func WithCheckpointImagePath(path string) CheckpointTaskOpts { - // WithRestoreImagePath sets image path for create option - func WithRestoreImagePath(path string) NewTaskOpts { - return func(ctx context.Context, c *Client, ti *TaskInfo) error { -- if ti.Options == nil { -- ti.Options = &options.Options{} -- } -- opts, ok := ti.Options.(*options.Options) -- if !ok { -- return errors.New("invalid runtime v2 options format") -+ opts, err := ti.getRuncOptions() -+ if err != nil { -+ return err - } - opts.CriuImagePath = path - return nil -@@ -134,12 +128,9 @@ func WithRestoreImagePath(path string) NewTaskOpts { - // WithRestoreWorkPath sets criu work path for create option - func WithRestoreWorkPath(path string) NewTaskOpts { - return func(ctx context.Context, c *Client, ti *TaskInfo) error { -- if ti.Options == nil { -- ti.Options = &options.Options{} -- } -- opts, ok := ti.Options.(*options.Options) -- if !ok { -- return errors.New("invalid runtime v2 options format") -+ opts, err := ti.getRuncOptions() -+ if err != nil { -+ return err - } - opts.CriuWorkPath = path - return nil -diff --git a/client/task_opts_unix.go b/client/task_opts_unix.go -index d33e302..26b5c17 100644 ---- a/client/task_opts_unix.go -+++ b/client/task_opts_unix.go -@@ -20,20 +20,14 @@ package client - - import ( - "context" -- "errors" -- -- "github.com/containerd/containerd/api/types/runc/options" - ) - - // WithNoNewKeyring causes tasks not to be created with a new keyring for secret storage. - // There is an upper limit on the number of keyrings in a linux system - func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error { -- if ti.Options == nil { -- ti.Options = &options.Options{} -- } -- opts, ok := ti.Options.(*options.Options) -- if !ok { -- return errors.New("invalid v2 shim create options format") -+ opts, err := ti.getRuncOptions() -+ if err != nil { -+ return err - } - opts.NoNewKeyring = true - return nil -@@ -41,12 +35,9 @@ func WithNoNewKeyring(ctx context.Context, c *Client, ti *TaskInfo) error { - - // WithNoPivotRoot instructs the runtime not to you pivot_root - func WithNoPivotRoot(_ context.Context, _ *Client, ti *TaskInfo) error { -- if ti.Options == nil { -- ti.Options = &options.Options{} -- } -- opts, ok := ti.Options.(*options.Options) -- if !ok { -- return errors.New("invalid v2 shim create options format") -+ opts, err := ti.getRuncOptions() -+ if err != nil { -+ return err - } - opts.NoPivotRoot = true - return nil -@@ -55,12 +46,9 @@ func WithNoPivotRoot(_ context.Context, _ *Client, ti *TaskInfo) error { - // WithShimCgroup sets the existing cgroup for the shim - func WithShimCgroup(path string) NewTaskOpts { - return func(ctx context.Context, c *Client, ti *TaskInfo) error { -- if ti.Options == nil { -- ti.Options = &options.Options{} -- } -- opts, ok := ti.Options.(*options.Options) -- if !ok { -- return errors.New("invalid v2 shim create options format") -+ opts, err := ti.getRuncOptions() -+ if err != nil { -+ return err - } - opts.ShimCgroup = path - return nil -@@ -70,12 +58,9 @@ func WithShimCgroup(path string) NewTaskOpts { - // WithUIDOwner allows console I/O to work with the remapped UID in user namespace - func WithUIDOwner(uid uint32) NewTaskOpts { - return func(ctx context.Context, c *Client, ti *TaskInfo) error { -- if ti.Options == nil { -- ti.Options = &options.Options{} -- } -- opts, ok := ti.Options.(*options.Options) -- if !ok { -- return errors.New("invalid v2 shim create options format") -+ opts, err := ti.getRuncOptions() -+ if err != nil { -+ return err - } - opts.IoUid = uid - return nil -@@ -85,12 +70,9 @@ func WithUIDOwner(uid uint32) NewTaskOpts { - // WithGIDOwner allows console I/O to work with the remapped GID in user namespace - func WithGIDOwner(gid uint32) NewTaskOpts { - return func(ctx context.Context, c *Client, ti *TaskInfo) error { -- if ti.Options == nil { -- ti.Options = &options.Options{} -- } -- opts, ok := ti.Options.(*options.Options) -- if !ok { -- return errors.New("invalid v2 shim create options format") -+ opts, err := ti.getRuncOptions() -+ if err != nil { -+ return err - } - opts.IoGid = gid - return nil --- -2.45.2 - diff --git a/SPECS/containerd2/CVE-2025-47911.patch b/SPECS/containerd2/CVE-2025-47911.patch deleted file mode 100644 index 2df8cafa55b..00000000000 --- a/SPECS/containerd2/CVE-2025-47911.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 532532d877df8bbee095441886578acaf619132c Mon Sep 17 00:00:00 2001 -From: Roland Shoemaker -Date: Mon, 29 Sep 2025 16:33:18 -0700 -Subject: [PATCH] html: impose open element stack size limit - -The HTML specification contains a number of algorithms which are -quadratic in complexity by design. Instead of adding complicated -workarounds to prevent these cases from becoming extremely expensive in -pathological cases, we impose a limit of 512 to the size of the stack of -open elements. It is extremely unlikely that non-adversarial HTML -documents will ever hit this limit (but if we see cases of this, we may -want to make the limit configurable via a ParseOption). - -Thanks to Guido Vranken and Jakub Ciolek for both independently -reporting this issue. - -Fixes CVE-2025-47911 -Fixes golang/go#75682 - -Change-Id: I890517b189af4ffbf427d25d3fde7ad7ec3509ad -Reviewed-on: https://go-review.googlesource.com/c/net/+/709876 -Reviewed-by: Damien Neil -LUCI-TryBot-Result: Go LUCI -Signed-off-by: Azure Linux Security Servicing Account -Upstream-reference: https://github.com/golang/net/commit/59706cdaa8f95502fdec64b67b4c61d6ca58727d.patch ---- - vendor/golang.org/x/net/html/escape.go | 2 +- - vendor/golang.org/x/net/html/parse.go | 21 +++++++++++++++++---- - 2 files changed, 18 insertions(+), 5 deletions(-) - -diff --git a/vendor/golang.org/x/net/html/escape.go b/vendor/golang.org/x/net/html/escape.go -index 04c6bec..12f2273 100644 ---- a/vendor/golang.org/x/net/html/escape.go -+++ b/vendor/golang.org/x/net/html/escape.go -@@ -299,7 +299,7 @@ func escape(w writer, s string) error { - case '\r': - esc = " " - default: -- panic("unrecognized escape character") -+ panic("html: unrecognized escape character") - } - s = s[i+1:] - if _, err := w.WriteString(esc); err != nil { -diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go -index 979ef17..4d12a1c 100644 ---- a/vendor/golang.org/x/net/html/parse.go -+++ b/vendor/golang.org/x/net/html/parse.go -@@ -231,7 +231,14 @@ func (p *parser) addChild(n *Node) { - } - - if n.Type == ElementNode { -- p.oe = append(p.oe, n) -+ p.insertOpenElement(n) -+ } -+} -+ -+func (p *parser) insertOpenElement(n *Node) { -+ p.oe = append(p.oe, n) -+ if len(p.oe) > 512 { -+ panic("html: open stack of elements exceeds 512 nodes") - } - } - -@@ -810,7 +817,7 @@ func afterHeadIM(p *parser) bool { - p.im = inFramesetIM - return true - case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title: -- p.oe = append(p.oe, p.head) -+ p.insertOpenElement(p.head) - defer p.oe.remove(p.head) - return inHeadIM(p) - case a.Head: -@@ -2320,9 +2327,13 @@ func (p *parser) parseCurrentToken() { - } - } - --func (p *parser) parse() error { -+func (p *parser) parse() (err error) { -+ defer func() { -+ if panicErr := recover(); panicErr != nil { -+ err = fmt.Errorf("%s", panicErr) -+ } -+ }() - // Iterate until EOF. Any other error will cause an early return. -- var err error - for err != io.EOF { - // CDATA sections are allowed only in foreign content. - n := p.oe.top() -@@ -2351,6 +2362,8 @@ func (p *parser) parse() error { - // s. Conversely, explicit s in r's data can be silently dropped, - // with no corresponding node in the resulting tree. - // -+// Parse will reject HTML that is nested deeper than 512 elements. -+// - // The input is assumed to be UTF-8 encoded. - func Parse(r io.Reader) (*Node, error) { - return ParseWithOptions(r) --- -2.45.4 - diff --git a/SPECS/containerd2/CVE-2025-58190.patch b/SPECS/containerd2/CVE-2025-58190.patch deleted file mode 100644 index 89b2b84a029..00000000000 --- a/SPECS/containerd2/CVE-2025-58190.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 582919df8cf0643cd434da7421238628ad5b4cb6 Mon Sep 17 00:00:00 2001 -From: Roland Shoemaker -Date: Mon, 29 Sep 2025 19:38:24 -0700 -Subject: [PATCH] html: align in row insertion mode with spec - -Update inRowIM to match the HTML specification. This fixes an issue -where a specific HTML document could cause the parser to enter an -infinite loop when trying to parse a and implied next to -each other. - -Fixes CVE-2025-58190 -Fixes golang/go#70179 - -Change-Id: Idcb133c87c7d475cc8c7eb1f1550ea21d8bdddea -Reviewed-on: https://go-review.googlesource.com/c/net/+/709875 -LUCI-TryBot-Result: Go LUCI -Reviewed-by: Damien Neil -Signed-off-by: Azure Linux Security Servicing Account -Upstream-reference: https://github.com/golang/net/commit/6ec8895aa5f6594da7356da7d341b98133629009.patch ---- - vendor/golang.org/x/net/html/parse.go | 36 ++++++++++++++++++--------- - 1 file changed, 24 insertions(+), 12 deletions(-) - -diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go -index 5b8374b..979ef17 100644 ---- a/vendor/golang.org/x/net/html/parse.go -+++ b/vendor/golang.org/x/net/html/parse.go -@@ -136,7 +136,7 @@ func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int { - return -1 - } - default: -- panic("unreachable") -+ panic(fmt.Sprintf("html: internal error: indexOfElementInScope unknown scope: %d", s)) - } - } - switch s { -@@ -179,7 +179,7 @@ func (p *parser) clearStackToContext(s scope) { - return - } - default: -- panic("unreachable") -+ panic(fmt.Sprintf("html: internal error: clearStackToContext unknown scope: %d", s)) - } - } - } -@@ -1674,7 +1674,7 @@ func inTableBodyIM(p *parser) bool { - return inTableIM(p) - } - --// Section 12.2.6.4.14. -+// Section 13.2.6.4.14. - func inRowIM(p *parser) bool { - switch p.tok.Type { - case StartTagToken: -@@ -1686,7 +1686,9 @@ func inRowIM(p *parser) bool { - p.im = inCellIM - return true - case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Tfoot, a.Thead, a.Tr: -- if p.popUntil(tableScope, a.Tr) { -+ if p.elementInScope(tableScope, a.Tr) { -+ p.clearStackToContext(tableRowScope) -+ p.oe.pop() - p.im = inTableBodyIM - return false - } -@@ -1696,22 +1698,28 @@ func inRowIM(p *parser) bool { - case EndTagToken: - switch p.tok.DataAtom { - case a.Tr: -- if p.popUntil(tableScope, a.Tr) { -+ if p.elementInScope(tableScope, a.Tr) { -+ p.clearStackToContext(tableRowScope) -+ p.oe.pop() - p.im = inTableBodyIM - return true - } - // Ignore the token. - return true - case a.Table: -- if p.popUntil(tableScope, a.Tr) { -+ if p.elementInScope(tableScope, a.Tr) { -+ p.clearStackToContext(tableRowScope) -+ p.oe.pop() - p.im = inTableBodyIM - return false - } - // Ignore the token. - return true - case a.Tbody, a.Tfoot, a.Thead: -- if p.elementInScope(tableScope, p.tok.DataAtom) { -- p.parseImpliedToken(EndTagToken, a.Tr, a.Tr.String()) -+ if p.elementInScope(tableScope, p.tok.DataAtom) && p.elementInScope(tableScope, a.Tr) { -+ p.clearStackToContext(tableRowScope) -+ p.oe.pop() -+ p.im = inTableBodyIM - return false - } - // Ignore the token. -@@ -2218,16 +2226,20 @@ func parseForeignContent(p *parser) bool { - p.acknowledgeSelfClosingTag() - } - case EndTagToken: -+ if strings.EqualFold(p.oe[len(p.oe)-1].Data, p.tok.Data) { -+ p.oe = p.oe[:len(p.oe)-1] -+ return true -+ } - for i := len(p.oe) - 1; i >= 0; i-- { -- if p.oe[i].Namespace == "" { -- return p.im(p) -- } - if strings.EqualFold(p.oe[i].Data, p.tok.Data) { - p.oe = p.oe[:i] -+ return true -+ } -+ if i > 0 && p.oe[i-1].Namespace == "" { - break - } - } -- return true -+ return p.im(p) - default: - // Ignore the token. - } --- -2.45.4 - diff --git a/SPECS/containerd2/CVE-2025-64329.patch b/SPECS/containerd2/CVE-2025-64329.patch deleted file mode 100644 index b742c82c321..00000000000 --- a/SPECS/containerd2/CVE-2025-64329.patch +++ /dev/null @@ -1,73 +0,0 @@ -From b9beeef78a6fd90ece5801780c45f550caf71b3d Mon Sep 17 00:00:00 2001 -From: wheat2018 <1151937289@qq.com> -Date: Tue, 13 Aug 2024 15:56:31 +0800 -Subject: [PATCH] fix goroutine leak of container Attach - -The monitor goroutine (runs (*ContainerIO).Attach.func1) of Attach will -never finish if it attaches to a container without any stdout or stderr -output. Wait for http context cancel and break the pipe actively to -address the issue. - -Signed-off-by: wheat2018 <1151937289@qq.com> -Signed-off-by: Akihiro Suda -Signed-off-by: Azure Linux Security Servicing Account -Upstream-reference: https://github.com/containerd/containerd/commit/083b53cd6f19b5de7717b0ce92c11bdf95e612df.patch ---- - internal/cri/io/container_io.go | 14 +++++++++++--- - internal/cri/server/container_attach.go | 2 +- - 2 files changed, 12 insertions(+), 4 deletions(-) - -diff --git a/internal/cri/io/container_io.go b/internal/cri/io/container_io.go -index 9fc5545..194634e 100644 ---- a/internal/cri/io/container_io.go -+++ b/internal/cri/io/container_io.go -@@ -17,6 +17,7 @@ - package io - - import ( -+ "context" - "errors" - "fmt" - "io" -@@ -160,7 +161,7 @@ func (c *ContainerIO) Pipe() { - - // Attach attaches container stdio. - // TODO(random-liu): Use pools.Copy in docker to reduce memory usage? --func (c *ContainerIO) Attach(opts AttachOptions) { -+func (c *ContainerIO) Attach(ctx context.Context, opts AttachOptions) { - var wg sync.WaitGroup - key := util.GenerateID() - stdinKey := streamKey(c.id, "attach-"+key, Stdin) -@@ -201,8 +202,15 @@ func (c *ContainerIO) Attach(opts AttachOptions) { - } - - attachStream := func(key string, close <-chan struct{}) { -- <-close -- log.L.Infof("Attach stream %q closed", key) -+ select { -+ case <-close: -+ log.L.Infof("Attach stream %q closed", key) -+ case <-ctx.Done(): -+ log.L.Infof("Attach client of %q cancelled", key) -+ // Avoid writeGroup heap up -+ c.stdoutGroup.Remove(key) -+ c.stderrGroup.Remove(key) -+ } - // Make sure stdin gets closed. - if stdinStreamRC != nil { - stdinStreamRC.Close() -diff --git a/internal/cri/server/container_attach.go b/internal/cri/server/container_attach.go -index 0147859..f4c3322 100644 ---- a/internal/cri/server/container_attach.go -+++ b/internal/cri/server/container_attach.go -@@ -82,6 +82,6 @@ func (c *criService) attachContainer(ctx context.Context, id string, stdin io.Re - }, - } - // TODO(random-liu): Figure out whether we need to support historical output. -- cntr.IO.Attach(opts) -+ cntr.IO.Attach(ctx, opts) - return nil - } --- -2.45.4 - diff --git a/SPECS/containerd2/containerd2.signatures.json b/SPECS/containerd2/containerd2.signatures.json index d49f7f913eb..ab5d6ac6a79 100644 --- a/SPECS/containerd2/containerd2.signatures.json +++ b/SPECS/containerd2/containerd2.signatures.json @@ -2,6 +2,6 @@ "Signatures": { "containerd.service": "a07bfcf412669b06673190b0779f48e652c9adcf1758289e849a00802804eec8", "containerd.toml": "5b3821236f09b4c858e0e098bbe1400f4dbbb47d360e39d21c61858b088c2896", - "containerd-2.0.0.tar.gz": "346d644e1b96e1f4a39bfe9d1eb0eb01ca676f806c12d95e5dbe35325bbc1780" + "containerd-2.2.2.tar.gz": "d6e8e6424c544cdab9b51cae320c3a9aa5590e8e1ffbd1f862eb395fd8c5bc28" } } \ No newline at end of file diff --git a/SPECS/containerd2/containerd2.spec b/SPECS/containerd2/containerd2.spec index 4c154bd5cf7..423e86820d6 100644 --- a/SPECS/containerd2/containerd2.spec +++ b/SPECS/containerd2/containerd2.spec @@ -1,11 +1,11 @@ %global debug_package %{nil} %define upstream_name containerd -%define commit_hash 207ad711eabd375a01713109a8a197d197ff6542 +%define commit_hash 301b2dac98f15c27117da5c8af12118a041a31d9 Summary: Industry-standard container runtime Name: %{upstream_name}2 -Version: 2.0.0 -Release: 18%{?dist} +Version: 2.2.2 +Release: 1%{?dist} License: ASL 2.0 Group: Tools/Container URL: https://www.containerd.io @@ -16,18 +16,8 @@ Source0: https://github.com/containerd/containerd/archive/v%{version}.tar.gz#/%{ Source1: containerd.service Source2: containerd.toml -Patch0: CVE-2024-45338.patch -Patch1: CVE-2025-27144.patch -Patch2: CVE-2024-40635.patch -Patch3: CVE-2025-22872.patch -Patch4: CVE-2025-47291.patch -Patch5: multi-snapshotters-support.patch -Patch6: tardev-support.patch -Patch7: CVE-2024-25621.patch -Patch8: CVE-2025-64329.patch -Patch9: fix-credential-leak-in-cri-errors.patch -Patch10:CVE-2025-47911.patch -Patch11:CVE-2025-58190.patch +Patch0: multi-snapshotters-support.patch +Patch1: tardev-support.patch %{?systemd_requires} BuildRequires: golang < 1.25 @@ -103,6 +93,13 @@ fi %dir /opt/containerd/lib %changelog +* Wed Apr 02 2026 Copilot - 2.2.2-1 +- Update to containerd 2.2.2 +- Remove CVE patches fixed upstream: CVE-2024-45338, CVE-2025-27144, + CVE-2024-40635, CVE-2025-22872, CVE-2025-47291, CVE-2024-25621, + CVE-2025-64329, CVE-2025-47911, CVE-2025-58190 +- Remove fix-credential-leak-in-cri-errors patch (included upstream) + * Thu Feb 12 2026 Azure Linux Security Servicing Account - 2.0.0-18 - Patch for CVE-2025-58190, CVE-2025-47911 diff --git a/SPECS/containerd2/fix-credential-leak-in-cri-errors.patch b/SPECS/containerd2/fix-credential-leak-in-cri-errors.patch deleted file mode 100644 index 909c179c259..00000000000 --- a/SPECS/containerd2/fix-credential-leak-in-cri-errors.patch +++ /dev/null @@ -1,401 +0,0 @@ -From a34e45d0fa2a7ddefff1a0871c9bf9e3c62bda17 Mon Sep 17 00:00:00 2001 -From: Andrey Noskov -Date: Thu, 6 Nov 2025 13:34:38 +0100 -Subject: [PATCH 1/2] fix: redact all query parameters in CRI error logs - -Signed-off-by: Andrey Noskov ---- - .../cri/instrument/instrumented_service.go | 8 ++ - internal/cri/util/sanitize.go | 93 +++++++++++++ - internal/cri/util/sanitize_test.go | 128 ++++++++++++++++++ - 3 files changed, 229 insertions(+) - create mode 100644 internal/cri/util/sanitize.go - create mode 100644 internal/cri/util/sanitize_test.go - -diff --git a/internal/cri/instrument/instrumented_service.go b/internal/cri/instrument/instrumented_service.go -index c2f5c8de99..f06315a6bd 100644 ---- a/internal/cri/instrument/instrumented_service.go -+++ b/internal/cri/instrument/instrumented_service.go -@@ -351,6 +351,8 @@ func (in *instrumentedService) PullImage(ctx context.Context, r *runtime.PullIma - log.G(ctx).Infof("PullImage %q", r.GetImage().GetImage()) - defer func() { - if err != nil { -+ // Sanitize error to remove sensitive information -+ err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("PullImage %q failed", r.GetImage().GetImage()) - } else { - log.G(ctx).Infof("PullImage %q returns image reference %q", -@@ -369,6 +371,8 @@ func (in *instrumentedService) ListImages(ctx context.Context, r *runtime.ListIm - log.G(ctx).Tracef("ListImages with filter %+v", r.GetFilter()) - defer func() { - if err != nil { -+ // Sanitize error to remove sensitive information -+ err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("ListImages with filter %+v failed", r.GetFilter()) - } else { - log.G(ctx).Tracef("ListImages with filter %+v returns image list %+v", -@@ -386,6 +390,8 @@ func (in *instrumentedService) ImageStatus(ctx context.Context, r *runtime.Image - log.G(ctx).Tracef("ImageStatus for %q", r.GetImage().GetImage()) - defer func() { - if err != nil { -+ // Sanitize error to remove sensitive information -+ err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("ImageStatus for %q failed", r.GetImage().GetImage()) - } else { - log.G(ctx).Tracef("ImageStatus for %q returns image status %+v", -@@ -404,6 +410,8 @@ func (in *instrumentedService) RemoveImage(ctx context.Context, r *runtime.Remov - log.G(ctx).Infof("RemoveImage %q", r.GetImage().GetImage()) - defer func() { - if err != nil { -+ // Sanitize error to remove sensitive information -+ err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("RemoveImage %q failed", r.GetImage().GetImage()) - } else { - log.G(ctx).Infof("RemoveImage %q returns successfully", r.GetImage().GetImage()) -diff --git a/internal/cri/util/sanitize.go b/internal/cri/util/sanitize.go -new file mode 100644 -index 0000000000..d50a15ebf6 ---- /dev/null -+++ b/internal/cri/util/sanitize.go -@@ -0,0 +1,93 @@ -+/* -+ 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 util -+ -+import ( -+ "errors" -+ "net/url" -+ "strings" -+) -+ -+// SanitizeError sanitizes an error by redacting sensitive information in URLs. -+// If the error contains a *url.Error, it parses and sanitizes the URL. -+// Otherwise, it returns the error unchanged. -+func SanitizeError(err error) error { -+ if err == nil { -+ return nil -+ } -+ -+ // Check if the error is or contains a *url.Error -+ var urlErr *url.Error -+ if errors.As(err, &urlErr) { -+ // Parse and sanitize the URL -+ sanitizedURL := sanitizeURL(urlErr.URL) -+ if sanitizedURL != urlErr.URL { -+ // Wrap with sanitized url.Error -+ return &sanitizedError{ -+ original: err, -+ sanitizedURL: sanitizedURL, -+ urlError: urlErr, -+ } -+ } -+ return err -+ } -+ -+ // No sanitization needed for non-URL errors -+ return err -+} -+ -+// sanitizeURL properly parses a URL and redacts all query parameters. -+func sanitizeURL(rawURL string) string { -+ parsed, err := url.Parse(rawURL) -+ if err != nil { -+ // If URL parsing fails, return original (malformed URLs shouldn't leak tokens) -+ return rawURL -+ } -+ -+ // Check if URL has query parameters -+ query := parsed.Query() -+ if len(query) == 0 { -+ return rawURL -+ } -+ -+ // Redact all query parameters -+ for param := range query { -+ query.Set(param, "[REDACTED]") -+ } -+ -+ // Reconstruct URL with sanitized query -+ parsed.RawQuery = query.Encode() -+ return parsed.String() -+} -+ -+// sanitizedError wraps an error containing a *url.Error with a sanitized URL. -+type sanitizedError struct { -+ original error -+ sanitizedURL string -+ urlError *url.Error -+} -+ -+// Error returns the error message with the sanitized URL. -+func (e *sanitizedError) Error() string { -+ // Replace all occurrences of the original URL with the sanitized version -+ return strings.ReplaceAll(e.original.Error(), e.urlError.URL, e.sanitizedURL) -+} -+ -+// Unwrap returns the original error for error chain traversal. -+func (e *sanitizedError) Unwrap() error { -+ return e.original -+} -diff --git a/internal/cri/util/sanitize_test.go b/internal/cri/util/sanitize_test.go -new file mode 100644 -index 0000000000..03e4fb2694 ---- /dev/null -+++ b/internal/cri/util/sanitize_test.go -@@ -0,0 +1,128 @@ -+/* -+ 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 util -+ -+import ( -+ "errors" -+ "fmt" -+ "net/url" -+ "testing" -+ -+ "github.com/stretchr/testify/assert" -+ "github.com/stretchr/testify/require" -+) -+ -+func TestSanitizeError_SimpleURLError(t *testing.T) { -+ // Create a url.Error with sensitive info -+ originalURL := "https://storage.blob.core.windows.net/container/blob?sig=SECRET&sv=2020" -+ urlErr := &url.Error{ -+ Op: "Get", -+ URL: originalURL, -+ Err: fmt.Errorf("connection timeout"), -+ } -+ -+ // Sanitize -+ sanitized := SanitizeError(urlErr) -+ require.NotNil(t, sanitized) -+ -+ // Check it's a sanitizedError with correct properties -+ sanitizedErr, ok := sanitized.(*sanitizedError) -+ require.True(t, ok, "Should return *sanitizedError type") -+ assert.Equal(t, urlErr, sanitizedErr.original) -+ assert.Equal(t, urlErr, sanitizedErr.urlError) -+ assert.Equal(t, "https://storage.blob.core.windows.net/container/blob?sig=%5BREDACTED%5D&sv=%5BREDACTED%5D", sanitizedErr.sanitizedURL) -+ -+ // Test Error() method - verifies ReplaceAll functionality -+ expected := "Get \"https://storage.blob.core.windows.net/container/blob?sig=%5BREDACTED%5D&sv=%5BREDACTED%5D\": connection timeout" -+ assert.Equal(t, expected, sanitized.Error()) -+} -+ -+func TestSanitizeError_WrappedError(t *testing.T) { -+ originalURL := "https://storage.blob.core.windows.net/blob?sig=SECRET&sv=2020" -+ urlErr := &url.Error{ -+ Op: "Get", -+ URL: originalURL, -+ Err: fmt.Errorf("timeout"), -+ } -+ -+ wrappedErr := fmt.Errorf("image pull failed: %w", urlErr) -+ -+ // Sanitize -+ sanitized := SanitizeError(wrappedErr) -+ -+ // Test Error() method with wrapped error - verifies ReplaceAll works in wrapped context -+ sanitizedMsg := sanitized.Error() -+ assert.NotContains(t, sanitizedMsg, "SECRET", "Secret should be sanitized") -+ assert.Contains(t, sanitizedMsg, "image pull failed", "Wrapper message should be preserved") -+ assert.Contains(t, sanitizedMsg, "%5BREDACTED%5D", "Should contain sanitized marker") -+ -+ // Should still be able to unwrap to url.Error -+ var targetURLErr *url.Error -+ assert.True(t, errors.As(sanitized, &targetURLErr), -+ "Should be able to find *url.Error in sanitized error chain") -+ -+ // Verify url.Error properties are preserved -+ assert.Equal(t, "Get", targetURLErr.Op) -+ assert.Contains(t, targetURLErr.Err.Error(), "timeout") -+} -+ -+func TestSanitizeError_NonURLError(t *testing.T) { -+ // Regular error without url.Error -+ regularErr := fmt.Errorf("some error occurred") -+ -+ sanitized := SanitizeError(regularErr) -+ -+ // Should return the exact same error object -+ assert.Equal(t, regularErr, sanitized, -+ "Non-URL errors should pass through unchanged") -+} -+ -+func TestSanitizeError_NilError(t *testing.T) { -+ sanitized := SanitizeError(nil) -+ assert.Nil(t, sanitized, "nil error should return nil") -+} -+ -+func TestSanitizeError_NoQueryParams(t *testing.T) { -+ // URL without any query parameters -+ urlErr := &url.Error{ -+ Op: "Get", -+ URL: "https://registry.example.com/v2/image/manifests/latest", -+ Err: fmt.Errorf("not found"), -+ } -+ -+ sanitized := SanitizeError(urlErr) -+ -+ // Should return the same error object (no sanitization needed) -+ assert.Equal(t, urlErr, sanitized, -+ "Errors without query params should pass through unchanged") -+} -+ -+func TestSanitizedError_Unwrap(t *testing.T) { -+ originalURL := "https://storage.blob.core.windows.net/blob?sig=SECRET" -+ urlErr := &url.Error{ -+ Op: "Get", -+ URL: originalURL, -+ Err: fmt.Errorf("timeout"), -+ } -+ -+ sanitized := SanitizeError(urlErr) -+ -+ // Should be able to unwrap -+ unwrapped := errors.Unwrap(sanitized) -+ assert.NotNil(t, unwrapped, "Should be able to unwrap sanitized error") -+ assert.Equal(t, urlErr, unwrapped, "Unwrapped should be the original error") -+} --- -2.45.4 - - -From 50e383e3907d04aeaec85853edfaa9ab34be1006 Mon Sep 17 00:00:00 2001 -From: Aadhar Agarwal -Date: Tue, 20 Jan 2026 22:16:30 +0000 -Subject: [PATCH 2/2] fix: sanitize error before gRPC return to prevent - credential leak in pod events - -PR #12491 fixed credential leaks in containerd logs but the gRPC error -returned to kubelet still contained sensitive information. This was -visible in Kubernetes pod events via `kubectl describe pod`. - -The issue was that SanitizeError was called inside the defer block, -but errgrpc.ToGRPC(err) was evaluated before the defer ran, so the -gRPC message contained the original unsanitized error. - -Move SanitizeError before the return statement so both the logged -error and the gRPC error are sanitized. - -Ref: #5453 -Signed-off-by: Aadhar Agarwal ---- - .../cri/instrument/instrumented_service.go | 24 ++++++++++++------- - 1 file changed, 16 insertions(+), 8 deletions(-) - -diff --git a/internal/cri/instrument/instrumented_service.go b/internal/cri/instrument/instrumented_service.go -index f06315a6bd..4379f95997 100644 ---- a/internal/cri/instrument/instrumented_service.go -+++ b/internal/cri/instrument/instrumented_service.go -@@ -351,8 +351,6 @@ func (in *instrumentedService) PullImage(ctx context.Context, r *runtime.PullIma - log.G(ctx).Infof("PullImage %q", r.GetImage().GetImage()) - defer func() { - if err != nil { -- // Sanitize error to remove sensitive information -- err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("PullImage %q failed", r.GetImage().GetImage()) - } else { - log.G(ctx).Infof("PullImage %q returns image reference %q", -@@ -361,6 +359,10 @@ func (in *instrumentedService) PullImage(ctx context.Context, r *runtime.PullIma - span.RecordError(err) - }() - res, err = in.c.PullImage(ctrdutil.WithNamespace(ctx), r) -+ // Sanitize error to remove sensitive information from both logs and returned gRPC error -+ if err != nil { -+ err = ctrdutil.SanitizeError(err) -+ } - return res, errgrpc.ToGRPC(err) - } - -@@ -371,8 +373,6 @@ func (in *instrumentedService) ListImages(ctx context.Context, r *runtime.ListIm - log.G(ctx).Tracef("ListImages with filter %+v", r.GetFilter()) - defer func() { - if err != nil { -- // Sanitize error to remove sensitive information -- err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("ListImages with filter %+v failed", r.GetFilter()) - } else { - log.G(ctx).Tracef("ListImages with filter %+v returns image list %+v", -@@ -380,6 +380,10 @@ func (in *instrumentedService) ListImages(ctx context.Context, r *runtime.ListIm - } - }() - res, err = in.c.ListImages(ctrdutil.WithNamespace(ctx), r) -+ // Sanitize error to remove sensitive information from both logs and returned gRPC error -+ if err != nil { -+ err = ctrdutil.SanitizeError(err) -+ } - return res, errgrpc.ToGRPC(err) - } - -@@ -390,8 +394,6 @@ func (in *instrumentedService) ImageStatus(ctx context.Context, r *runtime.Image - log.G(ctx).Tracef("ImageStatus for %q", r.GetImage().GetImage()) - defer func() { - if err != nil { -- // Sanitize error to remove sensitive information -- err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("ImageStatus for %q failed", r.GetImage().GetImage()) - } else { - log.G(ctx).Tracef("ImageStatus for %q returns image status %+v", -@@ -399,6 +401,10 @@ func (in *instrumentedService) ImageStatus(ctx context.Context, r *runtime.Image - } - }() - res, err = in.c.ImageStatus(ctrdutil.WithNamespace(ctx), r) -+ // Sanitize error to remove sensitive information from both logs and returned gRPC error -+ if err != nil { -+ err = ctrdutil.SanitizeError(err) -+ } - return res, errgrpc.ToGRPC(err) - } - -@@ -410,8 +416,6 @@ func (in *instrumentedService) RemoveImage(ctx context.Context, r *runtime.Remov - log.G(ctx).Infof("RemoveImage %q", r.GetImage().GetImage()) - defer func() { - if err != nil { -- // Sanitize error to remove sensitive information -- err = ctrdutil.SanitizeError(err) - log.G(ctx).WithError(err).Errorf("RemoveImage %q failed", r.GetImage().GetImage()) - } else { - log.G(ctx).Infof("RemoveImage %q returns successfully", r.GetImage().GetImage()) -@@ -419,6 +423,10 @@ func (in *instrumentedService) RemoveImage(ctx context.Context, r *runtime.Remov - span.RecordError(err) - }() - res, err := in.c.RemoveImage(ctrdutil.WithNamespace(ctx), r) -+ // Sanitize error to remove sensitive information from both logs and returned gRPC error -+ if err != nil { -+ err = ctrdutil.SanitizeError(err) -+ } - return res, errgrpc.ToGRPC(err) - } - --- -2.45.4 - diff --git a/cgmanifest.json b/cgmanifest.json index 9d049f8bc54..a4c9c16bffb 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -2067,8 +2067,8 @@ "type": "other", "other": { "name": "containerd2", - "version": "2.0.0", - "downloadUrl": "https://github.com/containerd/containerd/archive/v2.0.0.tar.gz" + "version": "2.2.2", + "downloadUrl": "https://github.com/containerd/containerd/archive/v2.2.2.tar.gz" } } },