diff --git a/Cargo.lock b/Cargo.lock index 12bde4aa7..f54983439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,6 @@ dependencies = [ "cni-plugins", "conntrack-tools", "containerd-1_7", - "containerd-2_1", "containerd-2_2", "coreutils", "cri-tools", @@ -250,13 +249,6 @@ dependencies = [ "glibc", ] -[[package]] -name = "containerd-2_1" -version = "0.1.0" -dependencies = [ - "glibc", -] - [[package]] name = "containerd-2_2" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 2eb2be679..ce16b152e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ members = [ "packages/cni-plugins", "packages/conntrack-tools", "packages/containerd-1.7", - "packages/containerd-2.1", "packages/containerd-2.2", "packages/coreutils", "packages/cri-tools", diff --git a/advisories/staging/BRSA-zosf9tq7lefr.toml b/advisories/staging/BRSA-zosf9tq7lefr.toml new file mode 100644 index 000000000..2cad0885c --- /dev/null +++ b/advisories/staging/BRSA-zosf9tq7lefr.toml @@ -0,0 +1,17 @@ +[advisory] +id = "BRSA-zosf9tq7lefr" +title = "containerd-2.1 support ended" +severity = "low" +end-of-life = true +description = "containerd-2.1 is no longer included in Bottlerocket. All users must upgrade to a version of the OS that does not include this package." + +[[advisory.products]] +package-name = "containerd-2.1" +patched-version = "2.1.8" +patched-epoch = "1" + +[updateinfo] +author = "shijiao" +issue-date = 2026-07-06T21:38:13Z +arches = ["aarch64", "x86_64"] +version = "staging" diff --git a/kits/bottlerocket-core-kit/Cargo.toml b/kits/bottlerocket-core-kit/Cargo.toml index b39998a93..c0890339b 100644 --- a/kits/bottlerocket-core-kit/Cargo.toml +++ b/kits/bottlerocket-core-kit/Cargo.toml @@ -26,7 +26,6 @@ cni = { path = "../../packages/cni" } cni-plugins = { path = "../../packages/cni-plugins" } conntrack-tools = { path = "../../packages/conntrack-tools" } containerd-1_7 = { path = "../../packages/containerd-1.7" } -containerd-2_1 = { path = "../../packages/containerd-2.1" } containerd-2_2 = { path = "../../packages/containerd-2.2" } coreutils = { path = "../../packages/coreutils" } cri-tools = { path = "../../packages/cri-tools" } diff --git a/packages/containerd-2.1/0001-cri-filter-CDI-annotations-on-checkpoint-restore.patch b/packages/containerd-2.1/0001-cri-filter-CDI-annotations-on-checkpoint-restore.patch deleted file mode 100644 index 27682562a..000000000 --- a/packages/containerd-2.1/0001-cri-filter-CDI-annotations-on-checkpoint-restore.patch +++ /dev/null @@ -1,268 +0,0 @@ -From 02045fd4696d21db11767226ff14576522809066 Mon Sep 17 00:00:00 2001 -From: Samuel Karp -Date: Mon, 8 Jun 2026 00:58:16 +0000 -Subject: [PATCH 4/5] 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 -Signed-off-by: Samuel Karp -(cherry picked from commit 861ffc1097685f9ecf13adaa381aca5fdf7ef0b4) -Signed-off-by: Chris Henzie ---- - .../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 ff1f6e6c0..721ffbca2 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 < "${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}" | sed 's/^/----> \t/' -@@ -192,6 +210,8 @@ function test_from_oci() { - - cat >"${TESTDIR}/config.toml" < -Date: Tue, 19 May 2026 03:25:13 +0000 -Subject: [PATCH 1/5] Bound user-database file reads in openBoundedUserFile - -Port of 7b05ec421 to the release/2.1 call shape, which reads user -databases via path-based user.ParsePasswdFile* / ParseGroupFile* -helpers rather than the fs.FS-based openUserFile wrapper introduced -later on main. - -UserFromPath, GIDFromPath, getSupplementalGroupsFromPath, and the -setAdditionalGids closure in WithAppendAdditionalGroups now open -/etc/passwd or /etc/group via openBoundedUserFile, which stats the -resolved path, refuses anything that is not a regular file, and -returns an io.ReadCloser that caps reads at maxUserFileBytes (10 -MiB). The reader is consumed by the user.ParsePasswdFilter / -ParseGroupFilter / ParseGroup variants. All four call sites read -either etc/passwd or etc/group; both are regular files on real -systems, well under the cap. - -The cap and the regular-file check together bound parser memory use -when reading user-database files of unexpected shape or size. - -Adds tests for the cap and for the non-regular file rejection. The -cap test covers three boundary points: a small pad (trailing entry -parsed), a pad placing the entry's last byte exactly on the cap -(still parsed), and a pad past the cap (read returns an "exceeds" -error). The non-regular test verifies a FIFO at etc/group is -rejected before any read is attempted. - -Updates TestWithAppendAdditionalGroupsNoEtcGroup to expect "stat -... no such file or directory" instead of "open ...". The -soft-on-missing behavior of WithAppendAdditionalGroups is unchanged. - -(cherry picked from commit 7b05ec421d0a07b33964c74145b6bf5dff58f476) -Signed-off-by: Chris Henzie -Signed-off-by: Brian Goff ---- - pkg/oci/spec_opts.go | 84 ++++++++++++++++++++- - pkg/oci/spec_opts_linux_test.go | 2 +- - pkg/oci/spec_opts_user_bounds_test.go | 101 ++++++++++++++++++++++++++ - 3 files changed, 182 insertions(+), 5 deletions(-) - create mode 100644 pkg/oci/spec_opts_user_bounds_test.go - -diff --git a/pkg/oci/spec_opts.go b/pkg/oci/spec_opts.go -index 7e0388882..dbd02e648 100644 ---- a/pkg/oci/spec_opts.go -+++ b/pkg/oci/spec_opts.go -@@ -22,6 +22,7 @@ import ( - "encoding/json" - "errors" - "fmt" -+ "io" - "math" - "os" - "path/filepath" -@@ -909,7 +910,12 @@ func WithAppendAdditionalGroups(groups ...string) SpecOpts { - if err != nil { - return err - } -- ugroups, groupErr := user.ParseGroupFile(gpath) -+ var ugroups []user.Group -+ f, groupErr := openBoundedUserFile(gpath) -+ if groupErr == nil { -+ ugroups, groupErr = user.ParseGroup(f) -+ f.Close() -+ } - if groupErr != nil && !os.IsNotExist(groupErr) { - return groupErr - } -@@ -1069,7 +1075,12 @@ func UserFromPath(root string, filter func(user.User) bool) (user.User, error) { - if err != nil { - return user.User{}, err - } -- users, err := user.ParsePasswdFileFilter(ppath, filter) -+ f, err := openBoundedUserFile(ppath) -+ if err != nil { -+ return user.User{}, err -+ } -+ defer f.Close() -+ users, err := user.ParsePasswdFilter(f, filter) - if err != nil { - return user.User{}, err - } -@@ -1089,7 +1100,12 @@ func GIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err err - if err != nil { - return 0, err - } -- groups, err := user.ParseGroupFileFilter(gpath, filter) -+ f, err := openBoundedUserFile(gpath) -+ if err != nil { -+ return 0, err -+ } -+ defer f.Close() -+ groups, err := user.ParseGroupFilter(f, filter) - if err != nil { - return 0, err - } -@@ -1105,7 +1121,12 @@ func getSupplementalGroupsFromPath(root string, filter func(user.Group) bool) ([ - if err != nil { - return []uint32{}, err - } -- groups, err := user.ParseGroupFileFilter(gpath, filter) -+ f, err := openBoundedUserFile(gpath) -+ if err != nil { -+ return []uint32{}, err -+ } -+ defer f.Close() -+ groups, err := user.ParseGroupFilter(f, filter) - if err != nil { - return []uint32{}, err - } -@@ -1658,3 +1679,58 @@ func WithWindowsNetworkNamespace(ns string) SpecOpts { - return nil - } - } -+ -+// maxUserFileBytes caps how much data is read from any user-database file -+// opened via openBoundedUserFile. Real systems keep these files well under -+// 1 MiB; 10 MiB is generous headroom while keeping peak memory during -+// user.ParsePasswd/ParseGroup bounded to single-digit MiB. -+const maxUserFileBytes = 10 << 20 -+ -+// openBoundedUserFile opens path and returns an io.ReadCloser that errors out -+// if more than maxUserFileBytes are read from it. Non-regular sources are -+// rejected before opening, so callers never block on FIFOs or device files -+// and parsers never consume bytes from them. -+// -+// openBoundedUserFile does NOT perform any path validation. It does not guard -+// against symlink traversal or paths that escape the container rootfs, and it -+// follows symlinks both when stat-ing and when opening. Callers are responsible -+// for confining path to the intended root beforehand (e.g. via fs.RootPath, -+// which resolves every symlink component and re-anchors absolute links to the -+// root) and must not pass attacker-controlled, unresolved paths directly. -+func openBoundedUserFile(path string) (io.ReadCloser, error) { -+ info, err := os.Stat(path) -+ if err != nil { -+ return nil, err -+ } -+ if !info.Mode().IsRegular() { -+ return nil, fmt.Errorf("%s is not a regular file", path) -+ } -+ f, err := os.Open(path) -+ if err != nil { -+ return nil, err -+ } -+ return &limitedFile{ -+ Closer: f, -+ // Allow one byte past the cap so an overflow surfaces as an -+ // error rather than a silent EOF that the parser would treat as -+ // a clean end-of-file (and miss any entries past the cap). -+ r: &io.LimitedReader{R: f, N: maxUserFileBytes + 1}, -+ name: path, -+ }, nil -+} -+ -+// limitedFile is an io.ReadCloser whose Read returns an error once more than -+// maxUserFileBytes have been read. -+type limitedFile struct { -+ io.Closer -+ r *io.LimitedReader -+ name string -+} -+ -+func (l *limitedFile) Read(p []byte) (int, error) { -+ n, err := l.r.Read(p) -+ if l.r.N == 0 { -+ return n, fmt.Errorf("%q exceeds %d bytes", l.name, maxUserFileBytes) -+ } -+ return n, err -+} -diff --git a/pkg/oci/spec_opts_linux_test.go b/pkg/oci/spec_opts_linux_test.go -index afb88bde7..dfa255857 100644 ---- a/pkg/oci/spec_opts_linux_test.go -+++ b/pkg/oci/spec_opts_linux_test.go -@@ -739,7 +739,7 @@ func TestWithAppendAdditionalGroupsNoEtcGroup(t *testing.T) { - { - name: "no additional gids, append root group", - groups: []string{"root"}, -- err: fmt.Sprintf("unable to find group root: open %s: no such file or directory", filepath.Join(td, "etc", "group")), -+ err: fmt.Sprintf("unable to find group root: stat %s: no such file or directory", filepath.Join(td, "etc", "group")), - expected: []uint32{0}, - }, - { -diff --git a/pkg/oci/spec_opts_user_bounds_test.go b/pkg/oci/spec_opts_user_bounds_test.go -new file mode 100644 -index 000000000..7a0a58f32 ---- /dev/null -+++ b/pkg/oci/spec_opts_user_bounds_test.go -@@ -0,0 +1,101 @@ -+//go:build !windows -+ -+/* -+ Copyright The containerd Authors. -+ -+ Licensed under the Apache License, Version 2.0 (the "License"); -+ you may not use this file except in compliance with the License. -+ You may obtain a copy of the License at -+ -+ http://www.apache.org/licenses/LICENSE-2.0 -+ -+ Unless required by applicable law or agreed to in writing, software -+ distributed under the License is distributed on an "AS IS" BASIS, -+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ See the License for the specific language governing permissions and -+ limitations under the License. -+*/ -+ -+package oci -+ -+import ( -+ "bytes" -+ "os" -+ "path/filepath" -+ "syscall" -+ "testing" -+ -+ "github.com/moby/sys/user" -+ "github.com/stretchr/testify/assert" -+) -+ -+// TestOpenBoundedUserFileCapsReads asserts the boundary behavior of the read -+// cap: well below, ending exactly at, and past maxUserFileBytes. -+func TestOpenBoundedUserFileCapsReads(t *testing.T) { -+ t.Parallel() -+ -+ beyond := []byte("\nbeyond:x:42:\n") -+ -+ for _, tc := range []struct { -+ name string -+ padBytes int -+ wantGids []uint32 -+ wantErr bool -+ }{ -+ { -+ name: "pad below cap, beyond is parsed", -+ padBytes: 100, -+ wantGids: []uint32{42}, -+ }, -+ { -+ name: "beyond ends exactly at cap, is parsed", -+ padBytes: maxUserFileBytes - len(beyond), -+ wantGids: []uint32{42}, -+ }, -+ { -+ name: "pad past cap, read errors out", -+ padBytes: maxUserFileBytes, -+ wantErr: true, -+ }, -+ } { -+ t.Run(tc.name, func(t *testing.T) { -+ t.Parallel() -+ -+ root := t.TempDir() -+ if err := os.MkdirAll(filepath.Join(root, "etc"), 0o755); err != nil { -+ t.Fatal(err) -+ } -+ data := append(bytes.Repeat([]byte{0}, tc.padBytes), beyond...) -+ if err := os.WriteFile(filepath.Join(root, "etc", "group"), data, 0o644); err != nil { -+ t.Fatal(err) -+ } -+ -+ gids, err := getSupplementalGroupsFromPath(root, func(g user.Group) bool { -+ return g.Name == "beyond" -+ }) -+ if tc.wantErr { -+ assert.ErrorContains(t, err, "exceeds") -+ return -+ } -+ assert.NoError(t, err) -+ assert.Equal(t, tc.wantGids, gids) -+ }) -+ } -+} -+ -+// TestOpenBoundedUserFileRejectsNonRegularFiles verifies that non-regular -+// files are refused before any byte is read from them. -+func TestOpenBoundedUserFileRejectsNonRegularFiles(t *testing.T) { -+ t.Parallel() -+ -+ root := t.TempDir() -+ if err := os.MkdirAll(filepath.Join(root, "etc"), 0o755); err != nil { -+ t.Fatal(err) -+ } -+ if err := syscall.Mkfifo(filepath.Join(root, "etc", "group"), 0o644); err != nil { -+ t.Fatal(err) -+ } -+ -+ _, err := getSupplementalGroupsFromPath(root, nil) -+ assert.ErrorContains(t, err, "not a regular file") -+} --- -2.54.0.1189.g8c84645362-goog - diff --git a/packages/containerd-2.1/0003-cri-do-not-re-tag-restored-checkpoints.patch b/packages/containerd-2.1/0003-cri-do-not-re-tag-restored-checkpoints.patch deleted file mode 100644 index b13bda661..000000000 --- a/packages/containerd-2.1/0003-cri-do-not-re-tag-restored-checkpoints.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 2e4583a9f7934961f47a5c4bba6ea779dce95635 Mon Sep 17 00:00:00 2001 -From: Samuel Karp -Date: Tue, 26 May 2026 16:06:58 -0700 -Subject: [PATCH 2/5] cri: do not re-tag restored checkpoints - -Google-Bug-Id: 508657842 -Signed-off-by: Samuel Karp -(cherry picked from commit 0c0918fa8fb4d997f889a3d811603995a3a2b68a) -Signed-off-by: Samuel Karp ---- - .../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 0e207d6c6..1d2652542 100644 ---- a/internal/cri/server/container_checkpoint_linux.go -+++ b/internal/cri/server/container_checkpoint_linux.go -@@ -322,23 +322,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.54.0.1189.g8c84645362-goog - diff --git a/packages/containerd-2.1/0004-Do-not-propagate-reserved-labels-from-image-configs.patch b/packages/containerd-2.1/0004-Do-not-propagate-reserved-labels-from-image-configs.patch deleted file mode 100644 index 8fcc03e06..000000000 --- a/packages/containerd-2.1/0004-Do-not-propagate-reserved-labels-from-image-configs.patch +++ /dev/null @@ -1,336 +0,0 @@ -From 429bcb924c278571f3d135933f562a87af1b6a53 Mon Sep 17 00:00:00 2001 -From: Ben Cressey -Date: Fri, 29 May 2026 21:33:28 +0000 -Subject: [PATCH 3/5] 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. - -Assisted-by: Claude Code -Signed-off-by: Ben Cressey -Signed-off-by: Samuel Karp -(cherry picked from commit 0ec1af4cae1256d18719ca892bf66340499e8050) -Signed-off-by: Akihiro Suda -Signed-off-by: Samuel Karp ---- - 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 66f532da7..27339884b 100644 ---- a/internal/cri/util/util.go -+++ b/internal/cri/util/util.go -@@ -78,11 +78,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 14118e1cb..f9ae31eb0 100644 ---- a/internal/cri/util/util_test.go -+++ b/internal/cri/util/util_test.go -@@ -141,21 +141,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.54.0.1189.g8c84645362-goog - diff --git a/packages/containerd-2.1/0005-cri-make-checkpoint-restore-robust-to-unexpected-arc.patch b/packages/containerd-2.1/0005-cri-make-checkpoint-restore-robust-to-unexpected-arc.patch deleted file mode 100644 index ccf730df9..000000000 --- a/packages/containerd-2.1/0005-cri-make-checkpoint-restore-robust-to-unexpected-arc.patch +++ /dev/null @@ -1,517 +0,0 @@ -From 570e69884650eb6b0bcee19edd812428adbac8c2 Mon Sep 17 00:00:00 2001 -From: Brian Goff -Date: Tue, 9 Jun 2026 11:37:18 -0700 -Subject: [PATCH 5/5] 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 /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. - -Signed-off-by: Brian Goff -(cherry picked from commit 8196411f24065533093be4c7ad874c23b06178f3) -Signed-off-by: Brian Goff -(cherry picked from commit 357652293053d0cd3ed565f718b0050aa662ae1a) -Signed-off-by: Brian Goff - -[rework container_checkpoint_linux_test.go to append instead of -creating a new file because a separate patch already creates it] -Signed-off-by: Piyush Jena ---- - .../cri/server/container_checkpoint_linux.go | 186 ++++++++++++++---- - .../server/container_checkpoint_linux_test.go | 131 ++++++++++++ - internal/cri/server/container_start.go | 35 ++-- - 3 files changed, 287 insertions(+), 65 deletions(-) - -diff --git a/internal/cri/server/container_checkpoint_linux.go b/internal/cri/server/container_checkpoint_linux.go -index 9fb5f2b1a..397b94c1d 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 -@@ -372,16 +454,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, -@@ -399,19 +504,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) - } - } -@@ -479,7 +586,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) - } -@@ -504,43 +629,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..9d6aec3b1 100644 ---- a/internal/cri/server/container_checkpoint_linux_test.go -+++ b/internal/cri/server/container_checkpoint_linux_test.go -@@ -22,7 +22,14 @@ import ( - "context" - "sync" - "testing" -+ "archive/tar" -+ "errors" -+ "os" -+ "path/filepath" - -+ "github.com/stretchr/testify/assert" -+ "github.com/stretchr/testify/require" -+ "golang.org/x/sys/unix" - "github.com/containerd/log" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" -@@ -105,3 +112,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.52.0 - diff --git a/packages/containerd-2.1/1001-Revert-Don-t-allow-io_uring-related-syscalls-in-the-.patch b/packages/containerd-2.1/1001-Revert-Don-t-allow-io_uring-related-syscalls-in-the-.patch deleted file mode 100644 index f60a92f3b..000000000 --- a/packages/containerd-2.1/1001-Revert-Don-t-allow-io_uring-related-syscalls-in-the-.patch +++ /dev/null @@ -1,72 +0,0 @@ -From f6f4f959862cad73cc44d07b088f449ae6c69066 Mon Sep 17 00:00:00 2001 -From: Henry Wang -Date: Thu, 10 Apr 2025 20:50:50 +0000 -Subject: [PATCH] Revert "Don't allow io_uring related syscalls in the - RuntimeDefault seccomp profile." - -This reverts commit a48ddf4a208b24eadea82f0eac62e236f2acf004. ---- - contrib/seccomp/seccomp_default.go | 3 +++ - contrib/seccomp/seccomp_default_test.go | 36 ------------------------- - 2 files changed, 3 insertions(+), 36 deletions(-) - delete mode 100644 contrib/seccomp/seccomp_default_test.go - -diff --git a/contrib/seccomp/seccomp_default.go b/contrib/seccomp/seccomp_default.go -index 55d673fcb..1135f2391 100644 ---- a/contrib/seccomp/seccomp_default.go -+++ b/contrib/seccomp/seccomp_default.go -@@ -188,6 +188,9 @@ func DefaultProfile(sp *specs.Spec) *specs.LinuxSeccomp { - "ioprio_set", - "io_setup", - "io_submit", -+ "io_uring_enter", -+ "io_uring_register", -+ "io_uring_setup", - "ipc", - "kill", - "landlock_add_rule", -diff --git a/contrib/seccomp/seccomp_default_test.go b/contrib/seccomp/seccomp_default_test.go -deleted file mode 100644 -index 53e386809..000000000 ---- a/contrib/seccomp/seccomp_default_test.go -+++ /dev/null -@@ -1,36 +0,0 @@ --package seccomp -- --import ( -- "testing" -- -- "github.com/opencontainers/runtime-spec/specs-go" --) -- --func TestIOUringIsNotAllowed(t *testing.T) { -- -- disallowed := map[string]bool{ -- "io_uring_enter": true, -- "io_uring_register": true, -- "io_uring_setup": true, -- } -- -- got := DefaultProfile(&specs.Spec{ -- Process: &specs.Process{ -- Capabilities: &specs.LinuxCapabilities{ -- Bounding: []string{}, -- }, -- }, -- }) -- -- for _, config := range got.Syscalls { -- if config.Action != specs.ActAllow { -- continue -- } -- -- for _, name := range config.Names { -- if disallowed[name] { -- t.Errorf("found disallowed io_uring related syscalls") -- } -- } -- } --} --- -2.45.0 - diff --git a/packages/containerd-2.1/1002-transfer-service-fallback-to-credentials.toml-for-re.patch b/packages/containerd-2.1/1002-transfer-service-fallback-to-credentials.toml-for-re.patch deleted file mode 100644 index e38a1661b..000000000 --- a/packages/containerd-2.1/1002-transfer-service-fallback-to-credentials.toml-for-re.patch +++ /dev/null @@ -1,137 +0,0 @@ -From 98bca202890467862de25819e0a1284e9afcfeeb Mon Sep 17 00:00:00 2001 -From: Ben Cressey -Date: Thu, 29 Jan 2026 21:52:41 +0000 -Subject: [PATCH] transfer-service: fallback to credentials.toml for registry - auth - -Signed-off-by: Ben Cressey ---- - core/transfer/registry/registry.go | 87 ++++++++++++++++++++++++++++-- - 1 file changed, 83 insertions(+), 4 deletions(-) - -diff --git a/core/transfer/registry/registry.go b/core/transfer/registry/registry.go -index a903bddfa..0f56c8a46 100644 ---- a/core/transfer/registry/registry.go -+++ b/core/transfer/registry/registry.go -@@ -18,13 +18,18 @@ package registry - - import ( - "context" -+ "encoding/base64" - "errors" - "fmt" - "io" - "net/http" -+ "os" -+ "path/filepath" - "strings" - "sync" - -+ "github.com/pelletier/go-toml/v2" -+ - transfertypes "github.com/containerd/containerd/api/types/transfer" - "github.com/containerd/containerd/v2/core/remotes" - "github.com/containerd/containerd/v2/core/remotes/docker" -@@ -39,6 +44,76 @@ import ( - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - ) - -+// hostDirectory converts ":port" to "_port_" in directory names. -+// Matches containerd's config/hosts.go encoding. -+func hostDirectory(host string) string { -+ idx := strings.LastIndex(host, ":") -+ if idx > 0 { -+ return host[:idx] + "_" + host[idx+1:] + "_" -+ } -+ return host -+} -+ -+func loadHostCredentials(ctx context.Context, hostDir, host string) (string, string, error) { -+ if hostDir == "" { -+ return "", "", nil -+ } -+ // Try encoded path first (registry_5000_), then literal (registry:5000) -+ paths := []string{filepath.Join(hostDir, hostDirectory(host), "credentials.toml")} -+ if hostDirectory(host) != host { -+ paths = append(paths, filepath.Join(hostDir, host, "credentials.toml")) -+ } -+ var d []byte -+ var err error -+ var credPath string -+ for _, p := range paths { -+ d, err = os.ReadFile(p) -+ if err == nil { -+ credPath = p -+ break -+ } -+ if !os.IsNotExist(err) { -+ return "", "", err -+ } -+ } -+ if d == nil { -+ return "", "", nil -+ } -+ var c struct { -+ Username string `toml:"username"` -+ Password string `toml:"password"` -+ IdentityToken string `toml:"identitytoken"` -+ Auth string `toml:"auth"` -+ } -+ if err := toml.Unmarshal(d, &c); err != nil { -+ log.G(ctx).WithError(err).Warnf("failed to parse credentials from %s", credPath) -+ return "", "", nil -+ } -+ if c.Username != "" || c.Password != "" { -+ log.G(ctx).WithField("path", credPath).Debug("using fallback credentials (username/password)") -+ return c.Username, c.Password, nil -+ } -+ if c.IdentityToken != "" { -+ log.G(ctx).WithField("path", credPath).Debug("using fallback credentials (identity token)") -+ return "", c.IdentityToken, nil -+ } -+ if c.Auth != "" { -+ dec, err := base64.StdEncoding.DecodeString(c.Auth) -+ if err != nil { -+ log.G(ctx).WithError(err).Warnf("failed to decode auth from %s", credPath) -+ return "", "", nil -+ } -+ user, passwd, ok := strings.Cut(string(dec), ":") -+ if !ok { -+ log.G(ctx).Warnf("invalid auth format in %s", credPath) -+ return "", "", nil -+ } -+ log.G(ctx).WithField("path", credPath).Debug("using fallback credentials (auth)") -+ return user, strings.Trim(passwd, "\x00"), nil -+ } -+ return "", "", nil -+} -+ - func init() { - // TODO: Move this to separate package? - plugins.Register(&transfertypes.OCIRegistry{}, &OCIRegistry{}) -@@ -134,8 +209,10 @@ func NewOCIRegistry(ctx context.Context, ref string, opts ...Opt) (*OCIRegistry, - if err != nil { - return "", "", err - } -- -- return c.Username, c.Secret, nil -+ if c.Username != "" || c.Secret != "" { -+ return c.Username, c.Secret, nil -+ } -+ return loadHostCredentials(ctx, ropts.hostDir, host) - } - } - if ropts.defaultScheme != "" { -@@ -376,8 +453,10 @@ func (r *OCIRegistry) UnmarshalAny(ctx context.Context, sm streaming.StreamGette - if err != nil { - return "", "", err - } -- -- return c.Username, c.Secret, nil -+ if c.Username != "" || c.Secret != "" { -+ return c.Username, c.Secret, nil -+ } -+ return loadHostCredentials(ctx, s.Resolver.HostDir, host) - } - } - r.headers = http.Header{} diff --git a/packages/containerd-2.1/1003-ctr-default-hosts-dir-to-etc-containerd-certs.d.patch b/packages/containerd-2.1/1003-ctr-default-hosts-dir-to-etc-containerd-certs.d.patch deleted file mode 100644 index 38697e4f5..000000000 --- a/packages/containerd-2.1/1003-ctr-default-hosts-dir-to-etc-containerd-certs.d.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 5996f17f4b3287148255c0cd89dee981f83dc0c2 Mon Sep 17 00:00:00 2001 -From: Ben Cressey -Date: Fri, 30 Jan 2026 19:08:27 +0000 -Subject: [PATCH] ctr: default hosts-dir to /etc/containerd/certs.d - -Signed-off-by: Ben Cressey ---- - cmd/ctr/commands/commands.go | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/cmd/ctr/commands/commands.go b/cmd/ctr/commands/commands.go -index 946da78ed..d4b7a3a87 100644 ---- a/cmd/ctr/commands/commands.go -+++ b/cmd/ctr/commands/commands.go -@@ -72,8 +72,8 @@ var ( - Usage: "Refresh token for authorization server", - }, - &cli.StringFlag{ -- Name: "hosts-dir", -- // compatible with "/etc/docker/certs.d" -+ Name: "hosts-dir", -+ Value: "/etc/containerd/certs.d", - Usage: "Custom hosts configuration directory", - }, - &cli.StringFlag{ diff --git a/packages/containerd-2.1/Cargo.toml b/packages/containerd-2.1/Cargo.toml deleted file mode 100644 index 50fd91363..000000000 --- a/packages/containerd-2.1/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "containerd-2_1" -version = "0.1.0" -edition = "2021" -publish = false -build = "../build.rs" - -[lib] -path = "../packages.rs" - -[package.metadata.build-package] -package-name = "containerd-2.1" -releases-url = "https://github.com/containerd/containerd/releases" - -[[package.metadata.build-package.external-files]] -url = "https://github.com/containerd/containerd/archive/v2.1.8/containerd-2.1.8.tar.gz" -sha512 = "52b7fe828f5bca14acd33b3125fc5ea94863444d142613de72e848f5a8f096248103d2a4f560919dfbeb00a445d2f745f5ca7a070108bd6a6097fbcd355b9b97" - -[build-dependencies] -glibc = { path = "../glibc" } diff --git a/packages/containerd-2.1/clarify.toml b/packages/containerd-2.1/clarify.toml deleted file mode 100644 index 69e32c016..000000000 --- a/packages/containerd-2.1/clarify.toml +++ /dev/null @@ -1,16 +0,0 @@ -[clarify."sigs.k8s.io/yaml"] -expression = "MIT AND BSD-3-Clause" -license-files = [ - { path = "LICENSE", hash = 0x617d80bc }, - { path = "goyaml.v2/LICENSE", hash = 0xe569d630 }, - { path = "goyaml.v2/LICENSE.libyaml", hash = 0xa2e4ce3 }, - { path = "goyaml.v2/NOTICE", hash = 0x49bceeb9 } - -] - -[clarify."github.com/grpc-ecosystem/go-grpc-middleware/v2"] -expression = "Apache-2.0" -license-files = [ - { path = "COPYRIGHT", hash = 0x4bba7b1 }, - { path = "LICENSE", hash = 0x6a39c900 } -] diff --git a/packages/containerd-2.1/containerd-2.1.spec b/packages/containerd-2.1/containerd-2.1.spec deleted file mode 100644 index 610e223d8..000000000 --- a/packages/containerd-2.1/containerd-2.1.spec +++ /dev/null @@ -1,160 +0,0 @@ -%global goproject github.com/containerd -%global gorepo containerd -%global goimport %{goproject}/%{gorepo} - -%global gover 2.1.8 -%global rpmver %{gover} -%global gitrev 97a35b7b64279cfcee8c568e7236a614c68bdca9 - -%global package_priority_epoch 1 -%global _dwz_low_mem_die_limit 0 - -Name: %{_cross_os}%{gorepo}-2.1 -Version: %{rpmver} -Release: 1%{?dist} -Summary: An industry-standard container runtime -License: Apache-2.0 -URL: https://%{goimport} -Source0: https://%{goimport}/archive/v%{gover}/%{gorepo}-%{gover}.tar.gz -Source1: containerd.service -Source2: containerd-config-toml_k8s_containerd_sock -Source3: containerd-config-toml_basic -Source4: containerd-config-toml_k8s_nvidia_containerd_sock -Source5: containerd-tmpfiles.conf -Source6: containerd-cri-base-json -Source7: snapshotter-toml - -# Mount for writing containerd configuration -Source100: etc-containerd.mount - -# Create container storage mount point. -Source110: prepare-var-lib-containerd.service - -# Drop-ins to disable igzip or pigz if the other implementation is preferred. -Source200: containerd-disable-igzip.conf -Source201: containerd-disable-pigz.conf - -Source1000: clarify.toml - -Patch0001: 0001-cri-filter-CDI-annotations-on-checkpoint-restore.patch -Patch0002: 0002-Bound-user-database-file-reads-in-openBoundedUserFil.patch -Patch0003: 0003-cri-do-not-re-tag-restored-checkpoints.patch -Patch0004: 0004-Do-not-propagate-reserved-labels-from-image-configs.patch -Patch0005: 0005-cri-make-checkpoint-restore-robust-to-unexpected-arc.patch - -# Patch to support moving from containerd-1.7 to 2.x -Patch1001: 1001-Revert-Don-t-allow-io_uring-related-syscalls-in-the-.patch -# Patch for transfer service to fall back to file for registry creds. -Patch1002: 1002-transfer-service-fallback-to-credentials.toml-for-re.patch -# Patch to make ctr use hosts.toml for registry mirrors by default -Patch1003: 1003-ctr-default-hosts-dir-to-etc-containerd-certs.d.patch - -BuildRequires: git -BuildRequires: %{_cross_os}glibc-devel -Requires: %{_cross_os}runc -Requires: %{name}(optimized-gunzip) - -Provides: %{_cross_os}%{gorepo} = %{package_priority_epoch}: -Conflicts: %{_cross_os}%{gorepo} - -%description -%{summary}. - -%package pigz -Summary: Prefer pigz for gzip decompression -Requires: %{_cross_os}pigz -Requires: %{name} -Provides: %{_cross_os}%{gorepo}-pigz = %{package_priority_epoch}: -Conflicts: %{name}-igzip -Provides: %{name}(optimized-gunzip) = 1: - -%description pigz -%{summary}. - -%package igzip -Summary: Prefer igzip for gzip decompression -Requires: %{_cross_os}igzip -Requires: %{name} -Provides: %{_cross_os}%{gorepo}-igzip = %{package_priority_epoch}: -Conflicts: %{name}-pigz -%if "%{_cross_arch}" == "x86_64" -Provides: %{name}(optimized-gunzip) = 2: -%else -Provides: %{name}(optimized-gunzip) = 0: -%endif - -%description igzip -%{summary}. - -%prep -%autosetup -Sgit -n %{gorepo}-%{gover} -p1 - -%build -%set_cross_go_flags - -export BUILDTAGS="no_btrfs selinux" -export LD_VERSION="-X github.com/containerd/containerd/v2/version.Version=%{gover}+bottlerocket" -export LD_REVISION="-X github.com/containerd/containerd/v2/version.Revision=%{gitrev}" - -declare -a BUILD_ARGS -BUILD_ARGS=( - -tags="${BUILDTAGS}" - -ldflags="${GOLDFLAGS} ${LD_VERSION} ${LD_REVISION}" -) - -for bin in \ - containerd \ - containerd-shim-runc-v2 \ - ctr ; -do - go build "${BUILD_ARGS[@]}" -o ${bin} ./cmd/${bin} -done - -%install -install -d %{buildroot}%{_cross_bindir} -for bin in \ - containerd \ - containerd-shim-runc-v2 \ - ctr ; -do - install -p -m 0755 ${bin} %{buildroot}%{_cross_bindir} -done - -install -d %{buildroot}%{_cross_unitdir} -install -p -m 0644 %{S:1} %{S:100} %{S:110} %{buildroot}%{_cross_unitdir} - -install -d %{buildroot}%{_cross_templatedir} -install -d %{buildroot}%{_cross_factorydir}%{_cross_sysconfdir}/containerd -install -p -m 0644 %{S:2} %{S:3} %{S:4} %{S:6} %{S:7} %{buildroot}%{_cross_templatedir} - -install -d %{buildroot}%{_cross_tmpfilesdir} -install -p -m 0644 %{S:5} %{buildroot}%{_cross_tmpfilesdir}/containerd.conf - -install -d %{buildroot}%{_cross_unitdir}/containerd.service.d -install -p -m 0644 %{S:200} %{buildroot}%{_cross_unitdir}/containerd.service.d/005-disable-igzip.conf -install -p -m 0644 %{S:201} %{buildroot}%{_cross_unitdir}/containerd.service.d/005-disable-pigz.conf - -%cross_scan_attribution --clarify %{S:1000} go-vendor vendor - -%files -%license LICENSE NOTICE -%{_cross_attribution_file} -%{_cross_attribution_vendor_dir} -%{_cross_unitdir}/containerd.service -%{_cross_unitdir}/etc-containerd.mount -%{_cross_unitdir}/prepare-var-lib-containerd.service -%dir %{_cross_factorydir}%{_cross_sysconfdir}/containerd -%{_cross_templatedir}/containerd-config-toml* -%{_cross_templatedir}/containerd-cri-base-json -%{_cross_templatedir}/snapshotter-toml -%{_cross_tmpfilesdir}/containerd.conf -%{_cross_bindir}/containerd -%{_cross_bindir}/containerd-shim-runc-v2 -%{_cross_bindir}/ctr -%files pigz -%{_cross_unitdir}/containerd.service.d/005-disable-igzip.conf - -%files igzip -%{_cross_unitdir}/containerd.service.d/005-disable-pigz.conf - -%changelog diff --git a/packages/containerd-2.1/containerd-config-toml_basic b/packages/containerd-2.1/containerd-config-toml_basic deleted file mode 100644 index 44c1f5686..000000000 --- a/packages/containerd-2.1/containerd-config-toml_basic +++ /dev/null @@ -1,34 +0,0 @@ -+++ -version = 3 -root = "/var/lib/containerd" -state = "/run/containerd" -disabled_plugins = [ - "io.containerd.internal.v1.opt", - "io.containerd.internal.v1.tracing", - "io.containerd.snapshotter.v1.blockfile", - "io.containerd.snapshotter.v1.devmapper", - "io.containerd.snapshotter.v1.native", - "io.containerd.snapshotter.v1.zfs", - "io.containerd.grpc.v1.cri", - "io.containerd.tracing.processor.v1.otlp", -] - -imports = ["/etc/containerd/config.d/*.toml"] - -[grpc] -address = "/run/containerd/containerd.sock" - -[plugins."io.containerd.transfer.v1.local"] -max_concurrent_downloads = 10 -concurrent_layer_fetch_buffer = 0 - -[[plugins."io.containerd.transfer.v1.local".unpack_config]] -snapshotter = "overlayfs" -differ = "walking" -{{#if (eq os.arch "aarch64")}} -platform = "linux/arm64" -{{else}} -{{#if (eq os.arch "x86_64")}} -platform = "linux/amd64" -{{/if}} -{{/if}} diff --git a/packages/containerd-2.1/containerd-config-toml_k8s_containerd_sock b/packages/containerd-2.1/containerd-config-toml_k8s_containerd_sock deleted file mode 100644 index 1e2a34bff..000000000 --- a/packages/containerd-2.1/containerd-config-toml_k8s_containerd_sock +++ /dev/null @@ -1,74 +0,0 @@ -[required-extensions] -container-registry = "v1" -container-runtime = "v1" -kubernetes = "v1" -std = { version = "v1", helpers = ["join_array", "default"]} -+++ -version = 3 -root = "/var/lib/containerd" -state = "/run/containerd" -disabled_plugins = [ - "io.containerd.internal.v1.opt", - "io.containerd.internal.v1.tracing", - "io.containerd.snapshotter.v1.blockfile", - "io.containerd.snapshotter.v1.devmapper", - "io.containerd.snapshotter.v1.native", - "io.containerd.snapshotter.v1.zfs", - "io.containerd.tracing.processor.v1.otlp", -] - -imports = ["/etc/containerd/config.d/*.toml"] - -[grpc] -address = "/run/containerd/containerd.sock" - -[plugins."io.containerd.cri.v1.images"] -use_local_image_pull = false - -# Pause container image is specified here, shares the same image as kubelet's pod-infra-container-image -[plugins."io.containerd.cri.v1.images".pinned_images] -sandbox = "localhost/kubernetes/pause:0.1.0" - -[plugins."io.containerd.cri.v1.runtime"] -device_ownership_from_security_context = {{default false settings.kubernetes.device-ownership-from-security-context}} -enable_selinux = true -{{#if settings.container-runtime.max-container-log-line-size}} -max_container_log_line_size = {{settings.container-runtime.max-container-log-line-size}} -{{/if}} -{{#if settings.container-runtime.enable-unprivileged-ports}} -enable_unprivileged_ports = {{settings.container-runtime.enable-unprivileged-ports}} -{{/if}} -{{#if settings.container-runtime.enable-unprivileged-icmp}} -enable_unprivileged_icmp = {{settings.container-runtime.enable-unprivileged-icmp}} -{{/if}} - -[plugins."io.containerd.transfer.v1.local"] -{{#if settings.container-runtime.max-concurrent-downloads}} -max_concurrent_downloads = {{settings.container-runtime.max-concurrent-downloads}} -{{/if}} -concurrent_layer_fetch_buffer = {{default 0 settings.container-runtime.concurrent-download-chunk-size}} - -[[plugins."io.containerd.transfer.v1.local".unpack_config]] -snapshotter = "overlayfs" -differ = "walking" -{{#if (eq os.arch "aarch64")}} -platform = "linux/arm64" -{{else}} -{{#if (eq os.arch "x86_64")}} -platform = "linux/amd64" -{{/if}} -{{/if}} - -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc] -runtime_type = "io.containerd.runc.v2" -base_runtime_spec = "/etc/containerd/cri-base.json" - -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc.options] -SystemdCgroup = true - -[plugins."io.containerd.cri.v1.runtime".cni] -bin_dirs = [ "/opt/cni/bin" ] -conf_dir = "/etc/cni/net.d" - -[plugins."io.containerd.cri.v1.images".registry] -config_path = "/etc/containerd/certs.d" diff --git a/packages/containerd-2.1/containerd-config-toml_k8s_nvidia_containerd_sock b/packages/containerd-2.1/containerd-config-toml_k8s_nvidia_containerd_sock deleted file mode 100644 index 04bb9a94e..000000000 --- a/packages/containerd-2.1/containerd-config-toml_k8s_nvidia_containerd_sock +++ /dev/null @@ -1,96 +0,0 @@ -[required-extensions] -container-registry = "v1" -container-runtime = "v1" -kubernetes = "v1" -std = { version = "v1", helpers = ["join_array", "default"]} -+++ -version = 3 -root = "/var/lib/containerd" -state = "/run/containerd" -disabled_plugins = [ - "io.containerd.internal.v1.opt", - "io.containerd.internal.v1.tracing", - "io.containerd.snapshotter.v1.blockfile", - "io.containerd.snapshotter.v1.devmapper", - "io.containerd.snapshotter.v1.native", - "io.containerd.snapshotter.v1.zfs", - "io.containerd.tracing.processor.v1.otlp", -] - -imports = ["/etc/containerd/config.d/*.toml"] - -[grpc] -address = "/run/containerd/containerd.sock" - -[plugins."io.containerd.cri.v1.images"] -use_local_image_pull = false - -# Pause container image is specified here, shares the same image as kubelet's pod-infra-container-image -[plugins."io.containerd.cri.v1.images".pinned_images] -sandbox = "localhost/kubernetes/pause:0.1.0" - -[plugins."io.containerd.cri.v1.runtime"] -device_ownership_from_security_context = {{default false settings.kubernetes.device-ownership-from-security-context}} -enable_selinux = true -{{#if settings.container-runtime.max-container-log-line-size}} -max_container_log_line_size = {{settings.container-runtime.max-container-log-line-size}} -{{/if}} -{{#if settings.container-runtime.enable-unprivileged-ports}} -enable_unprivileged_ports = {{settings.container-runtime.enable-unprivileged-ports}} -{{/if}} -{{#if settings.container-runtime.enable-unprivileged-icmp}} -enable_unprivileged_icmp = {{settings.container-runtime.enable-unprivileged-icmp}} -{{/if}} - -[plugins."io.containerd.transfer.v1.local"] -{{#if settings.container-runtime.max-concurrent-downloads}} -max_concurrent_downloads = {{settings.container-runtime.max-concurrent-downloads}} -{{/if}} -concurrent_layer_fetch_buffer = {{default 0 settings.container-runtime.concurrent-download-chunk-size}} - -[[plugins."io.containerd.transfer.v1.local".unpack_config]] -snapshotter = "overlayfs" -differ = "walking" -{{#if (eq os.arch "aarch64")}} -platform = "linux/arm64" -{{else}} -{{#if (eq os.arch "x86_64")}} -platform = "linux/amd64" -{{/if}} -{{/if}} - -[plugins."io.containerd.cri.v1.runtime".containerd] -default_runtime_name = "nvidia" - -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia] -runtime_type = "io.containerd.runc.v2" -base_runtime_spec = "/etc/containerd/cri-base.json" - -# CDI only nvidia container runtime -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia-cdi] -runtime_type = "io.containerd.runc.v2" -base_runtime_spec = "/etc/containerd/cri-base.json" - -# legacy only nvidia container runtime -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia-legacy] -runtime_type = "io.containerd.runc.v2" -base_runtime_spec = "/etc/containerd/cri-base.json" - -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia.options] -SystemdCgroup = true -BinaryName = "nvidia-container-runtime" - -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia-cdi.options] -SystemdCgroup = true -BinaryName = "nvidia-container-runtime.cdi" - -[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.nvidia-legacy.options] -SystemdCgroup = true -BinaryName = "nvidia-container-runtime.legacy" - -[plugins."io.containerd.cri.v1.runtime".cni] -bin_dirs = [ "/opt/cni/bin" ] -conf_dir = "/etc/cni/net.d" - -[plugins."io.containerd.cri.v1.images".registry] -config_path = "/etc/containerd/certs.d" diff --git a/packages/containerd-2.1/containerd-cri-base-json b/packages/containerd-2.1/containerd-cri-base-json deleted file mode 100644 index efbec56fc..000000000 --- a/packages/containerd-2.1/containerd-cri-base-json +++ /dev/null @@ -1,164 +0,0 @@ -[required-extensions] -oci-defaults = { version = "v1", helpers = ["oci_defaults"] } -+++ -{ - "ociVersion": "1.2.0", - "process": { - "user": { - "uid": 0, - "gid": 0 - }, - "cwd": "/", - {{~#if settings.oci-defaults.capabilities~}} - "capabilities": { - {{~oci_defaults "containerd" settings.oci-defaults.capabilities~}} - }, - {{~/if~}} - {{~#if settings.oci-defaults.resource-limits~}} - "rlimits": [ - {{~oci_defaults "containerd" settings.oci-defaults.resource-limits~}} - ], - {{~/if~}} - "noNewPrivileges": true - }, - "root": { - "path": "rootfs" - }, - "mounts": [ - { - "destination": "/proc", - "type": "proc", - "source": "proc", - "options": [ - "nosuid", - "noexec", - "nodev" - ] - }, - { - "destination": "/dev", - "type": "tmpfs", - "source": "tmpfs", - "options": [ - "nosuid", - "strictatime", - "mode=755", - "size=65536k" - ] - }, - { - "destination": "/dev/pts", - "type": "devpts", - "source": "devpts", - "options": [ - "nosuid", - "noexec", - "newinstance", - "ptmxmode=0666", - "mode=0620", - "gid=5" - ] - }, - { - "destination": "/dev/shm", - "type": "tmpfs", - "source": "shm", - "options": [ - "nosuid", - "noexec", - "nodev", - "mode=1777", - "size=65536k" - ] - }, - { - "destination": "/dev/mqueue", - "type": "mqueue", - "source": "mqueue", - "options": [ - "nosuid", - "noexec", - "nodev" - ] - }, - { - "destination": "/sys", - "type": "sysfs", - "source": "sysfs", - "options": [ - "nosuid", - "noexec", - "nodev", - "ro" - ] - }, - { - "destination": "/run", - "type": "tmpfs", - "source": "tmpfs", - "options": [ - "nosuid", - "strictatime", - "mode=755", - "size=65536k" - ] - }, - { - "destination": "/usr/local/sbin/modprobe", - "source": "/usr/bin/kmod", - "options": [ - "exec", - "bind", - "ro" - ] - } - ], - "linux": { - "resources": { - "devices": [ - { - "allow": false, - "access": "rwm" - } - ] - }, - "cgroupsPath": "/default", - "namespaces": [ - { - "type": "pid" - }, - { - "type": "ipc" - }, - { - "type": "uts" - }, - { - "type": "mount" - }, - { - "type": "network" - } - ], - "maskedPaths": [ - "/proc/acpi", - "/proc/asound", - "/proc/kcore", - "/proc/keys", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/sys/firmware", - "/sys/devices/virtual/powercap", - "/proc/scsi" - ], - "readonlyPaths": [ - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger" - ] - } -} diff --git a/packages/containerd-2.1/containerd-disable-igzip.conf b/packages/containerd-2.1/containerd-disable-igzip.conf deleted file mode 100644 index 346c8b4aa..000000000 --- a/packages/containerd-2.1/containerd-disable-igzip.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Service] -Environment=CONTAINERD_DISABLE_IGZIP=1 diff --git a/packages/containerd-2.1/containerd-disable-pigz.conf b/packages/containerd-2.1/containerd-disable-pigz.conf deleted file mode 100644 index cd2c2eb11..000000000 --- a/packages/containerd-2.1/containerd-disable-pigz.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Service] -Environment=CONTAINERD_DISABLE_PIGZ=1 diff --git a/packages/containerd-2.1/containerd-tmpfiles.conf b/packages/containerd-2.1/containerd-tmpfiles.conf deleted file mode 100644 index 8ea145f56..000000000 --- a/packages/containerd-2.1/containerd-tmpfiles.conf +++ /dev/null @@ -1,3 +0,0 @@ -d /etc/cni/net.d - - - - -d /etc/containerd/config.d - - - - -L+ /opt/containerd/image-verifier - - - - ../civ diff --git a/packages/containerd-2.1/containerd.service b/packages/containerd-2.1/containerd.service deleted file mode 100644 index ae7c19f9f..000000000 --- a/packages/containerd-2.1/containerd.service +++ /dev/null @@ -1,25 +0,0 @@ -[Unit] -Description=containerd container runtime -Documentation=https://containerd.io -After=network-online.target configured.target -Wants=network-online.target configured.target -Requires=configure-snapshotter.service - -[Service] -Slice=runtime.slice -EnvironmentFile=/etc/network/proxy.env -ExecStart=/usr/bin/containerd -Type=notify -Delegate=yes -KillMode=process -TimeoutSec=0 -RestartSec=2 -Restart=always -LimitNPROC=infinity -LimitCORE=infinity -LimitNOFILE=infinity -TasksMax=infinity -OOMScoreAdjust=-999 - -[Install] -WantedBy=multi-user.target diff --git a/packages/containerd-2.1/etc-containerd.mount b/packages/containerd-2.1/etc-containerd.mount deleted file mode 100644 index b7fdeb6cb..000000000 --- a/packages/containerd-2.1/etc-containerd.mount +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Containerd Configuration Directory (/etc/containerd) -DefaultDependencies=no -Conflicts=umount.target -Before=local-fs.target umount.target -After=selinux-policy-files.service -Wants=selinux-policy-files.service - -[Mount] -What=tmpfs -Where=/etc/containerd -Type=tmpfs -Options=nosuid,nodev,noexec,noatime,mode=0750,context=system_u:object_r:etc_secret_t:s0 - -[Install] -WantedBy=preconfigured.target diff --git a/packages/containerd-2.1/prepare-var-lib-containerd.service b/packages/containerd-2.1/prepare-var-lib-containerd.service deleted file mode 100644 index 285e90fa5..000000000 --- a/packages/containerd-2.1/prepare-var-lib-containerd.service +++ /dev/null @@ -1,23 +0,0 @@ -[Unit] -Description=Prepare Containerd Directory (/var/lib/containerd) -DefaultDependencies=no -RequiresMountsFor=/var -RefuseManualStart=true -RefuseManualStop=true - -[Service] -Type=oneshot - -# Remove an existing symlink, if present. Intentionally not recursive! -ExecStartPre=-/usr/bin/rm -f /var/lib/containerd - -# Create /var/lib/containerd so it is available for bind mounts. -ExecStart=/usr/bin/mkdir -p /var/lib/containerd - -# Suppress warning if directory exists. -StandardError=null - -RemainAfterExit=true - -[Install] -WantedBy=local-fs.target diff --git a/packages/containerd-2.1/snapshotter-toml b/packages/containerd-2.1/snapshotter-toml deleted file mode 100644 index 730173c7e..000000000 --- a/packages/containerd-2.1/snapshotter-toml +++ /dev/null @@ -1,26 +0,0 @@ -[required-extensions] -container-runtime = "v1" -std = { version = "v1" } -+++ -{{#if settings.container-runtime.snapshotter}} -{{#if (eq settings.container-runtime.snapshotter "soci" )}} -[plugins."io.containerd.cri.v1.images"] -snapshotter = "soci" - -[[plugins."io.containerd.transfer.v1.local".unpack_config]] -platform = "linux" -snapshotter = "soci" - -# Plug soci snapshotter into containerd -[proxy_plugins.soci] -capabilities = ["remap-ids"] -type = "snapshot" -address = "/run/soci-snapshotter/soci-snapshotter.sock" -[proxy_plugins.soci.exports] -root = "/var/lib/soci-snapshotter" -enable_remote_snapshot_annotations = "true" -{{else}} -[plugins."io.containerd.cri.v1.images"] -snapshotter = "overlayfs" -{{/if}} -{{/if}}