{"repro_id":"REPRO-2026-00372","version":6,"title":"CRI-O checkpoint restore bypasses destination Kubernetes security context","repro_type":"security","status":"published","severity":"high","cvss_score":8.8,"description":"CRI-O checkpoint restore bypasses the destination Kubernetes security context. Reported by lyhtheori (Red Hat bug 2535436; CVE published 2026-09-21 by secalert@redhat.com). Mechanism: when restoring a container from a checkpoint archive, or from an annotated OCI image pulled from a remote registry, CRIU restores process credentials, Linux capabilities, the no_new_privs bit, and seccomp state directly from the checkpoint data instead of re-enforcing the securityContext in the destination pod spec. An attacker with pod-creation privileges can craft a malicious checkpointed container image that, when restored, executes processes as root with full capabilities and no seccomp filtering while the runtime reports the requested restricted configuration. Checkpoint restore from remote registries is enabled by default in affected versions. Affected: upstream CRI-O >= 1.34 — vulnerable ranges 1.34.0–1.34.13, 1.35.0–1.35.8, 1.36.0–1.36.5; downstream Red Hat OpenShift Container Platform 4.17+ (Red Hat record additionally lists related conmon/podman/kata-containers components). Fixed upstream releases (tags published): v1.34.14, v1.35.9, v1.36.6. Fix commits: f4d95dfe70c4af4afff0c5a96e1a36975c2f65f9, 045d4107f10f9baa4e93d54a93762e0036942d4b, bb54fa0fba793889d815e5943abd6f8afc938c39 (release-1.36); cherry-pick equivalents release-1.35: 451d3adc54, a85fedccdc, e8e3219069; release-1.34: 254b3b3541. The fix reworks checkpoint/restore configuration (dedicated Checkpoint/Restore config block, default level checkpoint_only, simplified checkpoint-restore-level flag). Scoring/priority: CVSS v3.1 8.8 HIGH (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H; Red Hat severity Important); CWE-250, CWE-693; EPSS 0.0053 (~44th percentile); not in CISA KEV; no public PoC known. Repro plan (standalone single node; kubelet not required): 1) build/run a vulnerable CRI-O baseline (v1.35.8 or v1.36.5) with runc + CRIU + crictl; enable CRIU (enable_criu_support=true under [crio.runtime]); 2) start a source workload as root with broad capabilities and checkpoint it (crictl checkpoint); 3) restore the crafted checkpoint into a destination workload whose spec requests restrictions (runAsNonRoot, drop ALL capabilities, allowPrivilegeEscalation=false, seccomp RuntimeDefault / no_new_privs); 4) demonstrate retained checkpoint state — restored process runs as uid 0 with full CapEff and Seccomp:0 (/proc/<pid>/status) while crictl inspect reports the restricted requested config; 5) negative control — fixed release build v1.35.9 / v1.36.6 (or apply the fix commits): destination security context enforced after restore. Mitigations: disable checkpoint/restore (enable_criu_support=false; the ContainerCheckpoint feature gate is Tech Preview and disabled by default), restrict pod create/update RBAC, registry allowlisting, recreate containers previously restored from untrusted checkpoints.","root_cause":"## Summary\n\nCVE-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`.\n\n## Impact\n\n- **Package/component affected:** CRI-O container checkpoint import/restore path (`CreateContainer` -> `CRImportCheckpoint` -> runc/CRIU restore).\n- **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`.\n- **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.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Privilege escalation through CRI-O's service/API checkpoint restore workflow.\n- **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`.\n- **Parity:** full.\n- **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.\n\n## Root Cause\n\nIn 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.\n\nThe 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`:\n\n- Configuration split: https://github.com/cri-o/cri-o/commit/f4d95dfe70c4af4afff0c5a96e1a36975c2f65f9\n- Secure default (`checkpoint_only`): https://github.com/cri-o/cri-o/commit/045d4107f10f9baa4e93d54a93762e0036942d4b\n- Final flag naming: https://github.com/cri-o/cri-o/commit/bb54fa0fba793889d815e5943abd6f8afc938c39\n\nThe 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.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh` from any working directory. The script reads `bundle/project_cache_context.json`, uses the prepared `<project_cache_dir>/repo`, and falls back to `bundle/artifacts/cri-o` only when no usable prepared cache exists.\n2. 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.\n3. 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.\n4. 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`.\n5. 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/<pid>/status`.\n6. For two isolated final-fixed services, it submits the same archive/request. The secure `checkpoint_only` default rejects it before restore.\n7. Success is exit 0 plus `CONFIRMED:` output. The final script was executed twice consecutively; both runs returned 0.\n\n## Evidence\n\n- **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`.\n- **Vulnerable attempts:**\n  - `bundle/repro/proof/current/vulnerable-1.txt`\n  - `bundle/repro/proof/current/vulnerable-2.txt`\n  - `bundle/repro/proof/current/vulnerable-1-process-status.txt`\n  - `bundle/repro/proof/current/vulnerable-2-process-status.txt`\n- **Fixed negative controls:**\n  - `bundle/repro/proof/current/fixed-1.txt`\n  - `bundle/repro/proof/current/fixed-2.txt`\n- **Service logs:** `bundle/repro/proof/current/vulnerable-*-service.log` and `fixed-*-service.log`.\n- **Versions and identity:** `bundle/repro/proof/current/tool-versions.txt` and `source-identity.txt`.\n- **Diagnostics:** `bundle/logs/reproduction_steps.log` and `bundle/logs/repro/docker-driver.log`.\n\nKey current-run excerpts, repeated in both vulnerable attempts:\n\n```text\nrequested_run_as_user=65534\nrequested_no_new_privs=true\nrequested_drop_all=true\nrestore_result=accepted\nrestored_uid=0\nrestored_gid=0\nrestored_cap_eff=000001ffffffffff\nrestored_no_new_privs=0\nrestored_seccomp=0\n```\n\nThe 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:\n\n```text\nUid:    0    0    0    0\nGid:    0    0    0    0\nCapEff: 000001ffffffffff\nNoNewPrivs: 0\nSeccomp: 0\n```\n\nThe vulnerable service log records the real boundary and successful restore:\n\n```text\nname=/runtime.v1.RuntimeService/CreateContainer\nname=/runtime.v1.RuntimeService/StartContainer\nRestored container: <id>\n```\n\nThe 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.\n\n## Recommendations / Next Steps\n\n- Upgrade to v1.34.14, v1.35.9, v1.36.6, or a later supported release containing the full fix set.\n- Keep `container_level_enabled=\"checkpoint_only\"` (or `none`) unless restore is operationally required and all checkpoint sources are trusted.\n- Restrict pod/container creation and checkpoint-image registry permissions; allowlist registries and prevent untrusted archives from being used as container images.\n- Recreate containers previously restored from untrusted checkpoints after upgrading.\n- Add integration coverage that compares requested versus live `/proc/<pid>/status` values for UID/GID, all capability sets, `NoNewPrivs`, and seccomp after restore. Test both local archives and annotated OCI checkpoint images.\n- If restore is re-enabled explicitly, enforce destination process-security state or reject checkpoints whose serialized process state exceeds the destination policy.\n\n## Additional Notes\n\n- **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.\n- **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.\n- **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.\n- **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.\n","cve_id":"CVE-2026-92574","cwe_id":"CWE-250","source_url":"https://access.redhat.com/security/cve/CVE-2026-92574","package":{"name":"cri-o/cri-o","ecosystem":"go","affected_versions":">=1.34.0 <1.34.14"},"reproduced_at":"2026-09-24T17:38:16.458413+00:00","duration_secs":12009.0,"tool_calls":631,"handoffs":2,"total_cost_usd":100.226404,"agent_costs":{"claim_matcher":0.022787,"judge":1.073939,"learning_policy":0.013652,"repro":96.299353,"support":0.474128,"vuln_variant":2.342545},"cost_breakdown":{"claim_matcher":{"gpt-5.4-mini-2026-03-17":0.022787},"judge":{"gpt-5.6-sol":1.073939},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.013652},"repro":{"gpt-5.6-sol":96.299353},"support":{"gpt-5.6-sol":0.474128},"vuln_variant":{"gpt-5.6-sol":2.342545}},"vulnerable_version_variant_outcome":"unknown","fix_bypass_outcome":"unknown","variant_disclosure_state":"unknown","quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-09-24T17:38:17.455040+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":9928,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":6435,"category":"reproduction_script"},{"path":"bundle/repro/crio_checkpoint_driver.sh","filename":"crio_checkpoint_driver.sh","size":18716,"category":"other"},{"path":"bundle/repro/criu_rootless_uts.patch","filename":"criu_rootless_uts.patch","size":659,"category":"other"},{"path":"bundle/repro/proof/current/apt.log","filename":"apt.log","size":69426,"category":"log"},{"path":"bundle/repro/proof/current/fixed-1-service.log","filename":"fixed-1-service.log","size":12863,"category":"log"},{"path":"bundle/repro/proof/current/fixed-2-service.log","filename":"fixed-2-service.log","size":12864,"category":"log"},{"path":"bundle/repro/proof/current/source-identity.txt","filename":"source-identity.txt","size":226,"category":"other"},{"path":"bundle/repro/proof/current/source-process-status.txt","filename":"source-process-status.txt","size":76,"category":"other"},{"path":"bundle/repro/proof/current/source-service.log","filename":"source-service.log","size":17054,"category":"log"},{"path":"bundle/repro/proof/current/tool-versions.txt","filename":"tool-versions.txt","size":334,"category":"other"},{"path":"bundle/repro/proof/current/vulnerable-1-service.log","filename":"vulnerable-1-service.log","size":13256,"category":"log"},{"path":"bundle/repro/proof/current/vulnerable-2-service.log","filename":"vulnerable-2-service.log","size":13263,"category":"log"},{"path":"bundle/repro/runc_rootless_restore.patch","filename":"runc_rootless_restore.patch","size":1146,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":3324,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1317,"category":"other"}]}