# REPRO-2026-00372: CRI-O checkpoint restore bypasses destination Kubernetes security context ## Summary Status: published Severity: high CVSS: 8.8 / 10 CWE: CWE-250 Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00372 CVE: CVE-2026-92574 ## Package Name: cri-o/cri-o Ecosystem: go Affected: >=1.34.0 <1.34.14 Fixed: Unknown ## Root Cause ## Summary CVE-2026-92574 is confirmed in CRI-O's real CRI checkpoint/restore workflow. At vulnerable commit `6e902ee2a9f7ce535855c68c92c6838f087f578f` (the parent of the ticket's first fix commit), CRI-O accepts a CRIU checkpoint archive through `RuntimeService.CreateContainer` even when the destination `ContainerConfig` requests UID/GID 65534, `no_new_privs`, runtime-default seccomp, and all capabilities dropped. `RuntimeService.StartContainer` then restores the checkpoint's process state: both current-run attempts executed as UID/GID 0 with effective capabilities `000001ffffffffff`, `NoNewPrivs: 0`, and `Seccomp: 0`, while CRI inspection continued to report UID/GID 65534. The final fixed release-1.36 commit `bb54fa0fba793889d815e5943abd6f8afc938c39` defaults container restore to `checkpoint_only`; both controls rejected the same archive during `CreateContainer`. ## Impact - **Package/component affected:** CRI-O container checkpoint import/restore path (`CreateContainer` -> `CRImportCheckpoint` -> runc/CRIU restore). - **Affected versions:** Reported upstream ranges 1.34.0–1.34.13, 1.35.0–1.35.8, and 1.36.0–1.36.5. This run anchors the vulnerable source to commit `6e902ee2a9f7ce535855c68c92c6838f087f578f`, the resolved parent of fix commit `f4d95dfe70c4af4afff0c5a96e1a36975c2f65f9`. - **Risk level and consequences:** High. An actor able to cause CRI-O to restore an attacker-prepared checkpoint can bypass the destination security context and run a restored process with root credentials, all capabilities available in the runtime namespace, `no_new_privs` disabled, and seccomp disabled. This is a concrete privilege escalation relative to the requested destination policy. ## Impact Parity - **Disclosed/claimed maximum impact:** Privilege escalation through CRI-O's service/API checkpoint restore workflow. - **Reproduced impact from this run:** A real CRI-O endpoint restored an attacker checkpoint twice. Requested state was UID/GID 65534, drop all capabilities, `no_new_privs=true`, and runtime-default seccomp; observed live process state was UID/GID 0, `CapEff=000001ffffffffff`, `NoNewPrivs=0`, and `Seccomp=0`. - **Parity:** full. - **Not demonstrated:** The variant using an annotated checkpoint image fetched from a remote registry was not needed for impact parity; this run uses the supported checkpoint-archive image path across the same CRI endpoint. Node/host escape beyond the demonstrated destination container security-context boundary was not attempted. ## Root Cause In the vulnerable source, container restore is globally enabled by `enable_criu_support`. `server/container_create.go` recognizes a filesystem path in `ContainerConfig.image` as a checkpoint archive and dispatches to `CRImportCheckpoint`. The import path does copy the destination CRI `Linux.SecurityContext` into the newly registered container configuration, which is why `crictl inspect` reports the restricted UID/GID. However, starting a restore container asks runc/CRIU to recreate the serialized process from the checkpoint. CRIU restores the process credentials, capability sets, `no_new_privs`, and seccomp state from checkpoint images; those values therefore diverge from CRI-O's destination metadata. The upstream change set mitigates this by introducing explicit checkpoint/restore levels and defaulting container-level support to `checkpoint_only`, preventing archive/image restore unless an administrator intentionally opts back into `checkpoint_restore`: - Configuration split: https://github.com/cri-o/cri-o/commit/f4d95dfe70c4af4afff0c5a96e1a36975c2f65f9 - Secure default (`checkpoint_only`): https://github.com/cri-o/cri-o/commit/045d4107f10f9baa4e93d54a93762e0036942d4b - Final flag naming: https://github.com/cri-o/cri-o/commit/bb54fa0fba793889d815e5943abd6f8afc938c39 The fixed path's `RestoreContainerEnabled()` check no longer classifies the archive path as a checkpoint under the secure default. Both fixed controls therefore failed closed at `CreateContainer` rather than restoring the serialized privileged process. ## Reproduction Steps 1. Run `bundle/repro/reproduction_steps.sh` from any working directory. The script reads `bundle/project_cache_context.json`, uses the prepared `/repo`, and falls back to `bundle/artifacts/cri-o` only when no usable prepared cache exists. 2. The script resolves the vulnerable checkout as `f4d95dfe70c4af4afff0c5a96e1a36975c2f65f9^` and verifies that its full SHA is `6e902ee2a9f7ce535855c68c92c6838f087f578f`. It verifies the vulnerable source lacks `CheckpointRestoreConfig` and the fixed source contains it before building. 3. In a capability-enabled rootless Docker container, the helper builds/reuses commit-bound CRI-O binaries, pinned runc v1.3.4, and CRIU v4.1.1. Narrow compatibility patches compensate only for the worker's non-delegated cgroups and protected UTS sysctls; they do not modify checkpoint credentials, capabilities, `no_new_privs`, seccomp, CRI-O restore classification, or the fixed default. 4. The helper starts a real vulnerable CRI-O service, uses `crictl` over its Unix gRPC endpoint to create a root/full-capability/unconfined source container, and exports a real checkpoint archive through `CheckpointContainer`. 5. For two isolated vulnerable destination services, it uses `RunPodSandbox`, submits the checkpoint archive to `CreateContainer` with UID/GID 65534, drop-all, `no_new_privs=true`, and runtime-default seccomp, calls `StartContainer`, obtains the live PID through `ContainerStatus`, and reads `/proc//status`. 6. For two isolated final-fixed services, it submits the same archive/request. The secure `checkpoint_only` default rejects it before restore. 7. Success is exit 0 plus `CONFIRMED:` output. The final script was executed twice consecutively; both runs returned 0. ## Evidence - **Runtime manifest:** `bundle/repro/runtime_manifest.json` binds every immutable current-run artifact by SHA-256 and binds the source target to `git:https://github.com/cri-o/cri-o.git@6e902ee2a9f7ce535855c68c92c6838f087f578f`. - **Vulnerable attempts:** - `bundle/repro/proof/current/vulnerable-1.txt` - `bundle/repro/proof/current/vulnerable-2.txt` - `bundle/repro/proof/current/vulnerable-1-process-status.txt` - `bundle/repro/proof/current/vulnerable-2-process-status.txt` - **Fixed negative controls:** - `bundle/repro/proof/current/fixed-1.txt` - `bundle/repro/proof/current/fixed-2.txt` - **Service logs:** `bundle/repro/proof/current/vulnerable-*-service.log` and `fixed-*-service.log`. - **Versions and identity:** `bundle/repro/proof/current/tool-versions.txt` and `source-identity.txt`. - **Diagnostics:** `bundle/logs/reproduction_steps.log` and `bundle/logs/repro/docker-driver.log`. Key current-run excerpts, repeated in both vulnerable attempts: ```text requested_run_as_user=65534 requested_no_new_privs=true requested_drop_all=true restore_result=accepted restored_uid=0 restored_gid=0 restored_cap_eff=000001ffffffffff restored_no_new_privs=0 restored_seccomp=0 ``` The matching CRI inspection embedded in each attempt reports the running container's requested user as UID/GID 65534. The live process-status artifacts instead contain: ```text Uid: 0 0 0 0 Gid: 0 0 0 0 CapEff: 000001ffffffffff NoNewPrivs: 0 Seccomp: 0 ``` The vulnerable service log records the real boundary and successful restore: ```text name=/runtime.v1.RuntimeService/CreateContainer name=/runtime.v1.RuntimeService/StartContainer Restored container: ``` The fixed service logs report `Checkpoint/restore support enabled (level: "checkpoint_only")`; both `fixed-*.txt` files record `create_rc=1` and `restore_result=rejected` for the identical archive/input shape. ## Recommendations / Next Steps - Upgrade to v1.34.14, v1.35.9, v1.36.6, or a later supported release containing the full fix set. - Keep `container_level_enabled="checkpoint_only"` (or `none`) unless restore is operationally required and all checkpoint sources are trusted. - Restrict pod/container creation and checkpoint-image registry permissions; allowlist registries and prevent untrusted archives from being used as container images. - Recreate containers previously restored from untrusted checkpoints after upgrading. - Add integration coverage that compares requested versus live `/proc//status` values for UID/GID, all capability sets, `NoNewPrivs`, and seccomp after restore. Test both local archives and annotated OCI checkpoint images. - If restore is re-enabled explicitly, enforce destination process-security state or reject checkpoints whose serialized process state exceeds the destination policy. ## Additional Notes - **Idempotency:** Confirmed. The final `bundle/repro/reproduction_steps.sh` ran twice consecutively with exit code 0. Each invocation created a fresh checkpoint and fresh isolated vulnerable/fixed service attempts. - **Runtime constraints:** The worker exposes rootless Docker without delegated cgroups. The reproducer therefore uses a narrow runc/CRIU compatibility layer, declared as `bundle/repro/runc_rootless_restore.patch` and `bundle/repro/criu_rootless_uts.patch`, plus deterministic removal of worker-injected `/sys` mount records from the checkpoint image. None of these operations edit `core-1.img`, `seccomp.img`, process credentials, capabilities, or the destination request. - **Scope:** The proof crosses the actual CRI gRPC service boundary using `crictl`; it is not a direct package/unit harness. Unix-domain local transport is the standard standalone CRI endpoint, and the claim's attacker prerequisite remains authorization to create/restore containers through the service. - **Negative control semantics:** The fixed commit intentionally disables restore by default instead of transforming the restored credentials. Treating the archive path as a non-image and rejecting it is the expected fail-closed result. ## Reproduction Details Reproduced: 2026-09-24T17:38:16.458Z Duration: 12009 seconds Tool calls: 631 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00372 pruva-verify CVE-2026-92574 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00372&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00372/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-92574 - Source: https://access.redhat.com/security/cve/CVE-2026-92574 ## Artifacts - bundle/repro/rca_report.md (analysis, 9928 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 6435 bytes) - bundle/repro/crio_checkpoint_driver.sh (other, 18716 bytes) - bundle/repro/criu_rootless_uts.patch (other, 659 bytes) - bundle/repro/proof/current/apt.log (log, 69426 bytes) - bundle/repro/proof/current/fixed-1-service.log (log, 12863 bytes) - bundle/repro/proof/current/fixed-2-service.log (log, 12864 bytes) - bundle/repro/proof/current/source-identity.txt (other, 226 bytes) - bundle/repro/proof/current/source-process-status.txt (other, 76 bytes) - bundle/repro/proof/current/source-service.log (log, 17054 bytes) - bundle/repro/proof/current/tool-versions.txt (other, 334 bytes) - bundle/repro/proof/current/vulnerable-1-service.log (log, 13256 bytes) - bundle/repro/proof/current/vulnerable-2-service.log (log, 13263 bytes) - bundle/repro/runc_rootless_restore.patch (other, 1146 bytes) - bundle/repro/runtime_manifest.json (other, 3324 bytes) - bundle/repro/validation_verdict.json (other, 1317 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00372 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00372/artifacts/bundle/repro/reproduction_steps.sh - Web: https://www.pruva.dev/reproductions/REPRO-2026-00372 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://www.pruva.dev