# Patch Analysis: CVE-2026-50195 Variant Review

## Target and threat model reviewed

- Target: `github.com/containerd/containerd/v2`, CRI plugin checkpoint import path.
- Public policy / scope reviewed:
  - `README.md` points security reports to the containerd project security process and states that containerd has security audits and a formal vulnerability process.
  - `RELEASES.md` directs security issues to the same policy and documents security backport handling.
  - The upstream `containerd/project` `SECURITY.md` extraction confirms reports should be filed against the affected code repository and are triaged through GitHub Security Advisories.
- Relevant trust boundary: the CRI runtime API / kubelet-facing runtime socket. The ticket and reproduction treat a checkpoint archive/image supplied through CRI `CreateContainer` as attacker-controlled input crossing into the containerd host runtime. This is in scope for variant review. Purely local `ctr` operations by a trusted host administrator were not treated as vulnerability variants.

## What the fix changes

Primary file/function:

- `internal/cri/server/container_checkpoint_linux.go`
- function: `(*criService).CRImportCheckpoint`

The vulnerable v2.2.2 code path after reading untrusted checkpoint `config.dump`:

1. Reads `config.RootfsImageRef` from checkpoint metadata.
2. Calls `c.client.GetImage(ctx, config.RootfsImageRef)` and, if missing, `c.client.Pull(ctx, config.RootfsImageRef)`.
3. Parses `config.RootfsImageName` with `reference.ParseAnyReference`.
4. Calls `c.client.ImageService().Get(ctx, config.RootfsImageRef)`.
5. Mutates the image service record with `tagImage.Name = config.RootfsImageName`.
6. Calls `c.client.ImageService().Create(ctx, tagImage)`.

That final retagging step creates an attacker-selected local image name for the pulled attacker content. A later normal CRI container using that local name can run attacker-controlled image code.

The fixed v2.2.5 code removes the `ImageService().Get` / `tagImage.Name = config.RootfsImageName` / `ImageService().Create` block. It still validates `RootfsImageName` syntactically and still pulls/resolves `RootfsImageRef` for restore, but it no longer creates a local image tag from checkpoint metadata.

Additional hardening visible in the fixed branch includes regular-file/directory checks for checkpoint archive entries and OCI-image restore directory safety; those are separate hardening changes and not the tag-poisoning fix itself.

## Fix assumptions

The important fix assumption is narrow and effective: checkpoint metadata may still identify the rootfs image to restore, but it must not be allowed to create or overwrite arbitrary local image names. The fix does not rely on matching `RootfsImageRef` to `RootfsImageName`, parsing tricks, tag-vs-digest distinctions, or a blocklist of disallowed names. It removes the only image-service create operation in `CRImportCheckpoint` that materialized the untrusted `RootfsImageName` as a local tag.

## Code paths / inputs reviewed

Reviewed paths and candidates:

1. **Parent trigger: tar-file checkpoint archive through CRI `CreateContainer`**
   - Entrypoint: `internal/cri/server/container_create.go` detects `config.GetImage().GetImage()` as a local file and calls `CRImportCheckpoint`.
   - Sink: the removed image-service retagging block in `CRImportCheckpoint`.
   - Status: vulnerable on v2.2.2; blocked on v2.2.5.

2. **Candidate tested: digest-form `rootfsImageRef` (`NAME@DIGEST`) with attacker-controlled `rootfsImageName`**
   - Rationale: comments in the checkpoint import code state legitimate checkpoints store the base image as `NAME@DIGEST`; the original reproduction used a tag-form reference. This is a materially different data encoding for the pulled attacker image while crossing the same CRI trust boundary and reaching the same sink.
   - Runtime result: confirmed alternate trigger on vulnerable v2.2.2, but fixed v2.2.5 blocked the arbitrary tag creation. See `bundle/logs/vuln_variant/digest_variant_summary.txt`.

3. **OCI checkpoint image import path**
   - Entrypoint: `checkIfCheckpointOCIImage` resolves an image with `org.criu.checkpoint.container.name` annotation, mounts/copies its checkpoint contents, then calls the same `CRImportCheckpoint` logic.
   - Status: source review shows it reaches the same post-metadata sink, so the removal of the retagging block covers it as well. A separate runtime PoC was not necessary after the tested digest candidate demonstrated that the fixed code no longer creates arbitrary tags even when the base image is pulled/resolved.

4. **Local/admin `ctr` restore or image tag commands**
   - Entrypoint: containerd client/CLI APIs such as `ctr images tag` or `ctr containers restore`.
   - Status: not considered a valid vulnerability variant for this ticket because it does not cross the same CRI/kubelet trust boundary; it requires a trusted local containerd client/admin action and/or intentionally tagging an image.

## Behavior before and after the fix

Runtime-backed candidate: digest-form `rootfsImageRef`.

- Vulnerable v2.2.2 runtime (`containerd ... v2.2.2 301b2dac98f15c27117da5c8af12118a041a31d9`):
  - `rootfsImageRef=localhost:5000/attacker-digest@sha256:...`
  - `rootfsImageName=localhost:5000/victim-digest:stable`
  - After checkpoint import: attacker digest image entries = 1, poisoned victim tag entries = 1.
  - Later normal CRI `StartContainer` for `victim-digest:stable` executed `PRUVA_DIGEST_VARIANT_CODE_EXEC`.

- Fixed v2.2.5 runtime (`containerd ... v2.2.5 e53c7c1516c3b2bff98eb76f1f4117477e6f4e66`):
  - Same checkpoint metadata.
  - After checkpoint import: attacker digest image entries = 1, poisoned victim tag entries = 0.
  - Later normal victim `CreateContainer` failed with `failed to resolve image "localhost:5000/victim-digest:stable": not found`.
  - No attacker marker executed.

## Completeness assessment

No bypass of the v2.2.5 fix was found. The tested digest-form candidate is a meaningful alternate trigger for vulnerable versions because it uses the documented `NAME@DIGEST` form of `RootfsImageRef` and still reaches the same removed retagging sink. However, the fixed release blocks it for the same reason it blocks the parent trigger: untrusted `RootfsImageName` is no longer written into the local image store.

Recommendation: preserve the removal of all `ImageService().Create` / retag behavior sourced from checkpoint metadata in every `CRImportCheckpoint` entry path, including file-backed and OCI-image-backed checkpoints. Regression tests should include both tag-form and digest-form `RootfsImageRef` values where `RootfsImageName` differs and assert no local victim tag is created.
