# Variant Root Cause Analysis Report

## Summary

No bypass of the patched containerd CRI checkpoint-import fix was found. I tested a meaningful alternate trigger that uses a digest-form checkpoint `config.dump` field (`rootfsImageRef=NAME@DIGEST`) instead of the parent reproduction's tag-form `rootfsImageRef=NAME:TAG`, while still setting attacker-controlled `rootfsImageName` to an arbitrary victim tag. This alternate encoding reproduces the same tag-poisoning/code-execution chain on vulnerable containerd v2.2.2, but the fixed v2.2.5 runtime blocks it: the attacker base image is pulled/resolved, but no arbitrary victim tag is created and the later victim container cannot start.

## Fix Coverage / Assumptions

The fix relies on a direct invariant: untrusted checkpoint metadata may be used to identify the base image needed for checkpoint restore, but it must not create or overwrite local image names. In vulnerable v2.2.2, `internal/cri/server/container_checkpoint_linux.go` in `(*criService).CRImportCheckpoint` reads `config.RootfsImageRef`, obtains/pulls that image, then copies the image service record and calls `ImageService().Create` after replacing `tagImage.Name` with untrusted `config.RootfsImageName`. Fixed v2.2.5 removes that retagging block.

Covered code paths:

- CRI `CreateContainer` with a local checkpoint tar path, detected in `internal/cri/server/container_create.go` and passed to `CRImportCheckpoint`.
- CRI `CreateContainer` with an OCI checkpoint image resolved by `checkIfCheckpointOCIImage`, because after metadata extraction it reaches the same `CRImportCheckpoint` logic.
- Tag-form and digest-form `RootfsImageRef` values, because the fix removes creation of `RootfsImageName` regardless of the reference syntax.

No uncovered same-sink path was confirmed. Local trusted-administrator commands such as explicitly running `ctr images tag` are outside the CRI trust boundary and were not treated as valid vulnerability variants.

## Variant / Alternate Trigger

Tested alternate trigger: digest-form `rootfsImageRef` inside attacker-supplied checkpoint metadata.

- Entrypoint kind: `api_remote`.
- Entrypoint detail: CRI gRPC runtime endpoint, `CreateContainer` where `image.image` points to a checkpoint tar archive.
- Attacker-controlled fields:
  - `config.dump.rootfsImageRef=localhost:5000/attacker-digest@sha256:6015e02e94c8a0760f2ec65a62684d1ee6fb84925e68386f83e912ef8f9431af`
  - `config.dump.rootfsImageName=localhost:5000/victim-digest:stable`
- Code path:
  - `internal/cri/server/container_create.go`: checkpoint image/file detection and call to `CRImportCheckpoint`.
  - `internal/cri/server/container_checkpoint_linux.go`: `CRImportCheckpoint` reads `config.dump`, pulls/resolves `RootfsImageRef`, and, only in vulnerable versions, creates the arbitrary `RootfsImageName` tag.

The candidate is materially different from the parent reproduction because the pulled image identity uses `NAME@DIGEST`, which is also the form described in the checkpoint import comments for legitimate checkpoint metadata. It still crosses the same CRI trust boundary and reaches the same sink.

Runtime results:

- Vulnerable v2.2.2: alternate trigger confirmed. The import created both the attacker digest image and the victim tag, and later `StartContainer` for the victim tag executed attacker image code.
- Fixed v2.2.5: no bypass. The import pulled/resolved the attacker digest image but did not create the victim tag; later victim `CreateContainer` failed with image not found.

## Impact

- Package/component affected: `github.com/containerd/containerd/v2`, CRI checkpoint import implementation.
- Affected versions as tested for the alternate trigger: vulnerable official v2.2.2 runtime (`containerd ... v2.2.2 301b2dac98f15c27117da5c8af12118a041a31d9`) is affected.
- Fixed versions as tested: official v2.2.5 runtime (`containerd ... v2.2.5 e53c7c1516c3b2bff98eb76f1f4117477e6f4e66`) blocks the alternate trigger.
- Risk on vulnerable versions: critical, same as the parent issue. A checkpoint import can poison a local image tag so a later normal CRI container runs attacker-controlled image content.

## Impact Parity

- Disclosed/claimed maximum impact for parent: code execution through CRI checkpoint import by pulling/running attacker image content.
- Reproduced impact from this variant run: code execution on vulnerable v2.2.2. The victim container created from `localhost:5000/victim-digest:stable` printed `PRUVA_DIGEST_VARIANT_CODE_EXEC uid=0 gid=0 image=attacker-digest`.
- Parity: `partial` for variant-stage outcome overall. The alternate trigger has full impact parity on vulnerable versions, but it is not a bypass because fixed v2.2.5 blocks it.
- Not demonstrated: no code execution on the fixed target; that absence is the expected negative-control result.

## Root Cause

The same underlying root cause is reachable on vulnerable versions: checkpoint metadata is trusted as image-store authority. In v2.2.2, `CRImportCheckpoint` pulls/resolves `config.RootfsImageRef` and then creates a local image service record named by `config.RootfsImageName`. The candidate changes only the syntax of `RootfsImageRef` from a tag reference to a digest reference; the vulnerable sink still sees an image service record for the pulled digest and writes a new local victim tag for it.

The fix is effective because it removes the vulnerable image-service creation step rather than trying to validate a subset of reference syntaxes. Therefore the digest-form candidate does not bypass v2.2.5: `RootfsImageRef` may still be pulled, but `RootfsImageName` is not materialized as a local tag.

Fix commit identity was not resolved to a single upstream patch commit in this run, but the tested release identities were recorded:

- v2.2.2 tag source: `116b799b27bcb69462e26db2d70c4823211befeb`; release binary reports commit `301b2dac98f15c27117da5c8af12118a041a31d9`.
- v2.2.5 tag source: `bafc09e8fb5c3d754f57a77cd0b1fc6b7919834d`; release binary reports commit `e53c7c1516c3b2bff98eb76f1f4117477e6f4e66`.
- v2.3.2 tag source was also recorded as `c2be8504d753b8e360526e405e27c4164595daf6` for latest fixed-branch context.

## Reproduction Steps

1. Run `bundle/vuln_variant/reproduction_steps.sh` from the run root or with `PRUVA_ROOT` set to the bundle root.
2. The script:
   - Starts an isolated local Docker registry on a non-conflicting host port.
   - Builds/pushes a pause image, an attacker digest image, and a legitimate victim image.
   - Builds privileged runner images containing official containerd v2.2.2 and v2.2.5 release binaries.
   - For each runtime, drives the real CRI socket with `crictl runp`, `crictl create`, and `crictl start`.
   - Imports a malicious checkpoint archive where `rootfsImageRef` is `NAME@DIGEST` and `rootfsImageName` is a distinct victim tag.
   - Checks whether the victim tag was created and whether a later normal victim container executes attacker image code.
3. Expected evidence:
   - v2.2.2: `POST-CHECKPOINT: poisoned victim digest tag entries = 1` and `PRUVA_DIGEST_VARIANT_CODE_EXEC` in victim logs.
   - v2.2.5: `POST-CHECKPOINT: poisoned victim digest tag entries = 0`, victim `CreateContainer` fails with `not found`, and no attacker marker appears.

Per stage convention the script exits `1` because no fixed-version bypass was found, even though the script completed successfully and produced evidence. It was executed twice successfully and idempotently.

## Evidence

Primary evidence files:

- `bundle/logs/vuln_variant/digest_variant_summary.txt` — concise vulnerable/fixed verdict summary.
- `bundle/logs/vuln_variant/vulnerable_v222_digest_variant_evidence.txt` — full vulnerable run evidence.
- `bundle/logs/vuln_variant/fixed_v225_digest_variant_evidence.txt` — fixed negative-control evidence.
- `bundle/logs/vuln_variant/vulnerable_v222_digest_containerd_version.txt` — vulnerable runtime identity.
- `bundle/logs/vuln_variant/fixed_v225_digest_containerd_version.txt` — fixed runtime identity.
- `bundle/logs/vuln_variant/source_identity_discovery.txt` — source tag identities and patch hunk.
- `bundle/vuln_variant/runtime_manifest.json` — runtime manifest for the candidate attempt.

Key excerpts:

```text
Vulnerable result: VULN_DIGEST_TRIGGER_CONFIRMED
attacker digest ref=localhost:5000/attacker-digest@sha256:6015e02e94c8a0760f2ec65a62684d1ee6fb84925e68386f83e912ef8f9431af
POST-CHECKPOINT: attacker digest image entries = 1
POST-CHECKPOINT: poisoned victim digest tag entries = 1
PRUVA_DIGEST_VARIANT_CODE_EXEC uid=0 gid=0 image=attacker-digest
```

```text
Fixed result: FIXED_DIGEST_TRIGGER_BLOCKED
POST-CHECKPOINT: attacker digest image entries = 1
POST-CHECKPOINT: poisoned victim digest tag entries = 0
victim CreateContainer failed (expected for fixed runtime when no poisoned tag exists)
VERDICT: FIXED_DIGEST_TRIGGER_BLOCKED - fixed runtime pulled/resolved the base image but did not create the arbitrary victim tag
```

Environment details captured:

- Docker privileged runner with real containerd CRI plugin.
- Official containerd v2.2.2 and v2.2.5 release binaries.
- `runc`, CNI ptp/portmap, `crictl`, and local `registry:2`.

## Recommendations / Next Steps

- Preserve the v2.2.5 behavior that removes all `ImageService().Create` / arbitrary retagging based on checkpoint metadata.
- Add regression tests for both tag-form and digest-form `RootfsImageRef` values where `RootfsImageName` differs from the pulled image identity. The assertion should be that no `RootfsImageName` image-store record is created.
- Include both checkpoint archive and OCI checkpoint image import paths in regression coverage, since both converge on `CRImportCheckpoint` after metadata extraction.
- Treat future uses of checkpoint `RootfsImageName`, `RootfsImageRef`, `status.image`, and `status.image_ref` as untrusted metadata unless they are checked against authenticated image-store state.

## Additional Notes

- The target's public security policy/reporting docs were reviewed before variant classification.
- The stage-specific reproduction script was executed twice. Both runs completed and produced the same negative-bypass verdict.
- The script's nonzero exit code is intentional for this negative outcome: exit `0` is reserved for a confirmed fixed-version bypass; exit `1` means no bypass/variant on the fixed target was reproduced.
