Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian....@packages.debian.org
Usertags: pu
X-Debbugs-Cc: contain...@packages.debian.org, t...@security.debian.org, 
z...@debian.org
Control: affects -1 + src:containerd

[ Reason ]

Backport patches for 2 CVE:

* CVE-2023-25153: OCI image importer memory exhaustion
* CVE-2023-25173: Supplementary groups are not set up properly

[ Impact ]

[ Tests ]

CVE-2023-25153 is simple fix without test.
CVE-2023-25173 is covered by some unit tests and I've tested
it on a Kubernetes cluster.

[ Risks ]

The patch for CVE-2023-25153 is simple and cherry-picked from upstream
1.5 branch directly. It should not be risky.

The patch for CVE-2023-25173 is difficult to backport (many conflicts
when apply patches from 1.5 branch).
So I have rewrite it based on the commits on 1.5 branch. Especially this
commit https://github.com/containerd/containerd/commit/a62c38bf.
The 1.4.x package doesn't include CRI integration tests so I have to test
it by hand on a real Kubernetes cluster. In upstream a62c38bf commit message,
they have shown the tests cases. So I use them to verify on Kubernetes.

With containerd/1.4.13~ds1-1~deb11u3, the log is

NAMESPACE NAME                            READY   STATUS RESTARTS AGE
default   test-group-add-1-group-add-1234 0/1     Error  0        14m
default   test-no-option                  0/1     Error  0        14m
default   test-user-1234                  0/1     Error  0        14m
default   test-user-1234-1234             0/1     Error  0        14m
default   test-user-1234-group-add-1234   0/1     Error  0        14m

The container output is like

# kubectl logs test-no-option
+ id
+ '[' 'uid=0(root) gid=0(root) groups=10(wheel)' '=' 'uid=0(root) gid=0(root) 
groups=0(root),10(wheel)' ]

With containerd/1.4.13~ds1-1~deb11u4, the log is

NAMESPACE NAME                            READY   STATUS      RESTARTS  AGE
default   test-group-add-1-group-add-1234 0/1     Completed   0         4s
default   test-no-option                  0/1     Completed   0         4s
default   test-user-1234                  0/1     Completed   0         4s
default   test-user-1234-1234             0/1     Completed   0         4s
default   test-user-1234-group-add-1234   0/1     Completed   0         4s

The container output is like

# kubectl logs test-no-option
+ id
+ '[' 'uid=0(root) gid=0(root) groups=0(root),10(wheel)' '=' 'uid=0(root) 
gid=0(root) groups=0(root),10(wheel)' ]

It has passed the upstream tests.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]

See attachment.

[ Other info ]

No
diff -Nru containerd-1.4.13~ds1/debian/changelog 
containerd-1.4.13~ds1/debian/changelog
--- containerd-1.4.13~ds1/debian/changelog      2022-12-08 10:24:34.000000000 
+0800
+++ containerd-1.4.13~ds1/debian/changelog      2023-02-17 23:11:26.000000000 
+0800
@@ -1,3 +1,10 @@
+containerd (1.4.13~ds1-1~deb11u4) bullseye; urgency=medium
+
+  * CVE-2023-25153: OCI image importer memory exhaustion
+  * CVE-2023-25173: Supplementary groups are not set up properly
+
+ -- Shengjing Zhu <z...@debian.org>  Fri, 17 Feb 2023 23:11:26 +0800
+
 containerd (1.4.13~ds1-1~deb11u3) bullseye; urgency=medium
 
   * CVE-2022-23471: CRI plugin: Fix goroutine leak during Exec
diff -Nru containerd-1.4.13~ds1/debian/patches/0012-CVE-2023-25153.patch 
containerd-1.4.13~ds1/debian/patches/0012-CVE-2023-25153.patch
--- containerd-1.4.13~ds1/debian/patches/0012-CVE-2023-25153.patch      
1970-01-01 08:00:00.000000000 +0800
+++ containerd-1.4.13~ds1/debian/patches/0012-CVE-2023-25153.patch      
2023-02-17 23:11:26.000000000 +0800
@@ -0,0 +1,41 @@
+From: Samuel Karp <samuelk...@google.com>
+Date: Thu, 12 Jan 2023 18:06:41 -0800
+Subject: CVE-2023-25153
+
+Origin: backport, https://github.com/containerd/containerd/commit/959e1cf9
+---
+ images/archive/importer.go | 13 +++++++------
+ 1 file changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/images/archive/importer.go b/images/archive/importer.go
+index 2d04658..f24ab87 100644
+--- a/images/archive/importer.go
++++ b/images/archive/importer.go
+@@ -24,7 +24,6 @@ import (
+       "encoding/json"
+       "fmt"
+       "io"
+-      "io/ioutil"
+       "path"
+ 
+       "github.com/containerd/containerd/archive/compression"
+@@ -222,12 +221,14 @@ func ImportIndex(ctx context.Context, store 
content.Store, reader io.Reader, opt
+       return writeManifest(ctx, store, idx, ocispec.MediaTypeImageIndex)
+ }
+ 
++const (
++      kib       = 1024
++      mib       = 1024 * kib
++      jsonLimit = 20 * mib
++)
++
+ func onUntarJSON(r io.Reader, j interface{}) error {
+-      b, err := ioutil.ReadAll(r)
+-      if err != nil {
+-              return err
+-      }
+-      return json.Unmarshal(b, j)
++      return json.NewDecoder(io.LimitReader(r, jsonLimit)).Decode(j)
+ }
+ 
+ func onUntarBlob(ctx context.Context, r io.Reader, store content.Ingester, 
size int64, ref string) (digest.Digest, error) {
diff -Nru containerd-1.4.13~ds1/debian/patches/0013-CVE-2023-25173.patch 
containerd-1.4.13~ds1/debian/patches/0013-CVE-2023-25173.patch
--- containerd-1.4.13~ds1/debian/patches/0013-CVE-2023-25173.patch      
1970-01-01 08:00:00.000000000 +0800
+++ containerd-1.4.13~ds1/debian/patches/0013-CVE-2023-25173.patch      
2023-02-17 23:11:26.000000000 +0800
@@ -0,0 +1,316 @@
+From: Shengjing Zhu <z...@debian.org>
+Date: Fri, 17 Feb 2023 23:08:38 +0800
+Subject: CVE-2023-25173
+
+Origin: backport, https://github.com/containerd/containerd/commit/a62c38bf
+---
+ oci/spec_opts.go                                   |  21 ++
+ oci/spec_opts_linux_test.go                        | 212 +++++++++++++++++++++
+ .../cri/pkg/server/container_create_unix.go        |   3 +-
+ 3 files changed, 235 insertions(+), 1 deletion(-)
+ create mode 100644 oci/spec_opts_linux_test.go
+
+diff --git a/oci/spec_opts.go b/oci/spec_opts.go
+index 1372584..13ed7dd 100644
+--- a/oci/spec_opts.go
++++ b/oci/spec_opts.go
+@@ -114,6 +114,17 @@ func setCapabilities(s *Spec) {
+       }
+ }
+ 
++// ensureAdditionalGids ensures that the primary GID is also included in the 
additional GID list.
++func ensureAdditionalGids(s *Spec) {
++      setProcess(s)
++      for _, f := range s.Process.User.AdditionalGids {
++              if f == s.Process.User.GID {
++                      return
++              }
++      }
++      s.Process.User.AdditionalGids = append([]uint32{s.Process.User.GID}, 
s.Process.User.AdditionalGids...)
++}
++
+ // WithDefaultSpec returns a SpecOpts that will populate the spec with default
+ // values.
+ //
+@@ -503,7 +514,9 @@ func WithNamespacedCgroup() SpecOpts {
+ //   user, uid, user:group, uid:gid, uid:group, user:gid
+ func WithUser(userstr string) SpecOpts {
+       return func(ctx context.Context, client Client, c 
*containers.Container, s *Spec) error {
++              defer ensureAdditionalGids(s)
+               setProcess(s)
++              s.Process.User.AdditionalGids = nil
+               parts := strings.Split(userstr, ":")
+               switch len(parts) {
+               case 1:
+@@ -582,7 +595,9 @@ func WithUser(userstr string) SpecOpts {
+ // WithUIDGID allows the UID and GID for the Process to be set
+ func WithUIDGID(uid, gid uint32) SpecOpts {
+       return func(_ context.Context, _ Client, _ *containers.Container, s 
*Spec) error {
++              defer ensureAdditionalGids(s)
+               setProcess(s)
++              s.Process.User.AdditionalGids = nil
+               s.Process.User.UID = uid
+               s.Process.User.GID = gid
+               return nil
+@@ -595,7 +610,9 @@ func WithUIDGID(uid, gid uint32) SpecOpts {
+ // additionally sets the gid to 0, and does not return an error.
+ func WithUserID(uid uint32) SpecOpts {
+       return func(ctx context.Context, client Client, c 
*containers.Container, s *Spec) (err error) {
++              defer ensureAdditionalGids(s)
+               setProcess(s)
++              s.Process.User.AdditionalGids = nil
+               if c.Snapshotter == "" && c.SnapshotKey == "" {
+                       if !isRootfsAbs(s.Root.Path) {
+                               return errors.Errorf("rootfs absolute path is 
required")
+@@ -648,7 +665,9 @@ func WithUserID(uid uint32) SpecOpts {
+ // it returns error.
+ func WithUsername(username string) SpecOpts {
+       return func(ctx context.Context, client Client, c 
*containers.Container, s *Spec) (err error) {
++              defer ensureAdditionalGids(s)
+               setProcess(s)
++              s.Process.User.AdditionalGids = nil
+               if s.Linux != nil {
+                       if c.Snapshotter == "" && c.SnapshotKey == "" {
+                               if !isRootfsAbs(s.Root.Path) {
+@@ -703,7 +722,9 @@ func WithAdditionalGIDs(userstr string) SpecOpts {
+                       return nil
+               }
+               setProcess(s)
++              s.Process.User.AdditionalGids = nil
+               setAdditionalGids := func(root string) error {
++                      defer ensureAdditionalGids(s)
+                       var username string
+                       uid, err := strconv.Atoi(userstr)
+                       if err == nil {
+diff --git a/oci/spec_opts_linux_test.go b/oci/spec_opts_linux_test.go
+new file mode 100644
+index 0000000..29cb3f8
+--- /dev/null
++++ b/oci/spec_opts_linux_test.go
+@@ -0,0 +1,212 @@
++/*
++   Copyright The containerd Authors.
++
++   Licensed under the Apache License, Version 2.0 (the "License");
++   you may not use this file except in compliance with the License.
++   You may obtain a copy of the License at
++
++       http://www.apache.org/licenses/LICENSE-2.0
++
++   Unless required by applicable law or agreed to in writing, software
++   distributed under the License is distributed on an "AS IS" BASIS,
++   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++   See the License for the specific language governing permissions and
++   limitations under the License.
++*/
++
++package oci
++
++import (
++      "context"
++      "fmt"
++      "testing"
++
++      "github.com/containerd/containerd/containers"
++      "github.com/containerd/continuity/fs/fstest"
++      specs "github.com/opencontainers/runtime-spec/specs-go"
++      "github.com/stretchr/testify/assert"
++)
++
++// nolint:gosec
++func TestWithUserID(t *testing.T) {
++      t.Parallel()
++
++      expectedPasswd := `root:x:0:0:root:/root:/bin/ash
++guest:x:405:100:guest:/dev/null:/sbin/nologin
++`
++      td := t.TempDir()
++      apply := fstest.Apply(
++              fstest.CreateDir("/etc", 0777),
++              fstest.CreateFile("/etc/passwd", []byte(expectedPasswd), 0777),
++      )
++      if err := apply.Apply(td); err != nil {
++              t.Fatalf("failed to apply: %v", err)
++      }
++      c := containers.Container{ID: t.Name()}
++      testCases := []struct {
++              userID      uint32
++              expectedUID uint32
++              expectedGID uint32
++      }{
++              {
++                      userID:      0,
++                      expectedUID: 0,
++                      expectedGID: 0,
++              },
++              {
++                      userID:      405,
++                      expectedUID: 405,
++                      expectedGID: 100,
++              },
++              {
++                      userID:      1000,
++                      expectedUID: 1000,
++                      expectedGID: 0,
++              },
++      }
++      for _, testCase := range testCases {
++              testCase := testCase
++              t.Run(fmt.Sprintf("user %d", testCase.userID), func(t 
*testing.T) {
++                      t.Parallel()
++                      s := Spec{
++                              Version: specs.Version,
++                              Root: &specs.Root{
++                                      Path: td,
++                              },
++                              Linux: &specs.Linux{},
++                      }
++                      err := 
WithUserID(testCase.userID)(context.Background(), nil, &c, &s)
++                      assert.NoError(t, err)
++                      assert.Equal(t, testCase.expectedUID, 
s.Process.User.UID)
++                      assert.Equal(t, testCase.expectedGID, 
s.Process.User.GID)
++              })
++      }
++}
++
++// nolint:gosec
++func TestWithUsername(t *testing.T) {
++      t.Parallel()
++
++      expectedPasswd := `root:x:0:0:root:/root:/bin/ash
++guest:x:405:100:guest:/dev/null:/sbin/nologin
++`
++      td := t.TempDir()
++      apply := fstest.Apply(
++              fstest.CreateDir("/etc", 0777),
++              fstest.CreateFile("/etc/passwd", []byte(expectedPasswd), 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:        "root",
++                      expectedUID: 0,
++                      expectedGID: 0,
++              },
++              {
++                      user:        "guest",
++                      expectedUID: 405,
++                      expectedGID: 100,
++              },
++              {
++                      user: "1000",
++                      err:  "no users found",
++              },
++              {
++                      user: "unknown",
++                      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 := 
WithUsername(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 TestWithAdditionalGIDs(t *testing.T) {
++      t.Parallel()
++      expectedPasswd := `root:x:0:0:root:/root:/bin/ash
++bin:x:1:1:bin:/bin:/sbin/nologin
++daemon:x:2:2:daemon:/sbin:/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
++`
++      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
++              expected []uint32
++      }{
++              {
++                      user:     "root",
++                      expected: []uint32{0, 1, 2, 3},
++              },
++              {
++                      user:     "1000",
++                      expected: []uint32{0},
++              },
++              {
++                      user:     "bin",
++                      expected: []uint32{0, 2, 3},
++              },
++              {
++                      user:     "bin:root",
++                      expected: []uint32{0},
++              },
++              {
++                      user:     "daemon",
++                      expected: []uint32{0, 1},
++              },
++      }
++      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,
++                              },
++                      }
++                      err := 
WithAdditionalGIDs(testCase.user)(context.Background(), nil, &c, &s)
++                      assert.NoError(t, err)
++                      assert.Equal(t, testCase.expected, 
s.Process.User.AdditionalGids)
++              })
++      }
++}
+diff --git 
a/vendor/github.com/containerd/cri/pkg/server/container_create_unix.go 
b/vendor/github.com/containerd/cri/pkg/server/container_create_unix.go
+index bbe55e2..262ea44 100644
+--- a/vendor/github.com/containerd/cri/pkg/server/container_create_unix.go
++++ b/vendor/github.com/containerd/cri/pkg/server/container_create_unix.go
+@@ -299,7 +299,8 @@ func (c *criService) containerSpecOpts(config 
*runtime.ContainerConfig, imageCon
+               // Because it is still useful to get additional gids for uid 0.
+               userstr = 
strconv.FormatInt(securityContext.GetRunAsUser().GetValue(), 10)
+       }
+-      specOpts = append(specOpts, customopts.WithAdditionalGIDs(userstr))
++      specOpts = append(specOpts, customopts.WithAdditionalGIDs(userstr),
++                      
customopts.WithSupplementalGroups(securityContext.GetSupplementalGroups()))
+ 
+       apparmorSpecOpts, err := generateApparmorSpecOpts(
+               securityContext.GetApparmorProfile(),
diff -Nru containerd-1.4.13~ds1/debian/patches/series 
containerd-1.4.13~ds1/debian/patches/series
--- containerd-1.4.13~ds1/debian/patches/series 2022-12-08 10:24:34.000000000 
+0800
+++ containerd-1.4.13~ds1/debian/patches/series 2023-02-17 23:11:26.000000000 
+0800
@@ -9,3 +9,5 @@
 0009-CVE-2022-31030.patch
 0010-CVE-2022-24769.patch
 0011-CVE-2022-23471.patch
+0012-CVE-2023-25153.patch
+0013-CVE-2023-25173.patch

Reply via email to