#!/bin/bash
set -euo pipefail

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

exec > >(tee -a "$LOGS/variant_reproduction_steps.log")
exec 2>&1

echo "=== CVE-2026-10536 variant analysis: curl_easy_duphandle + CURLOPT_STREAM_DEPENDS UAF ==="
echo "ROOT=$ROOT"

CACHE_CTX="$ROOT/project_cache_context.json"
if [ -f "$CACHE_CTX" ] && jq -e '.prepared == true' "$CACHE_CTX" >/dev/null 2>&1; then
  PROJECT_CACHE_DIR="$(jq -r '.project_cache_dir' "$CACHE_CTX")"
else
  PROJECT_CACHE_DIR="$ARTIFACTS/cache"
fi
REPO_DIR="$PROJECT_CACHE_DIR/repo"
VULN_INSTALL="$PROJECT_CACHE_DIR/install-vuln"
FIXED_INSTALL="$PROJECT_CACHE_DIR/install-fixed"
LATEST_INSTALL="$ARTIFACTS/install-latest"

VULN_COMMIT="6e3f8dc1f173b47de9a68516ce4b95bf25598c2f"  # curl-8_20_0
FIXED_COMMIT="bfbff7852f050232edd3e5ca5c6bf2021c340f5a" # http2: remove stream dependency tracking
LATEST_TAG="curl-8_21_0"
LATEST_COMMIT="68720b4837" # tag curl-8_21_0

# Prefer prebuilt installs from the project cache (prepared by the repro stage).
# If the latest install is not available, skip the latest run but still test the
# patched (fixed) version, which is the minimum required for a bypass check.

if [ ! -f "$VULN_INSTALL/lib/libcurl.a" ] || [ ! -f "$FIXED_INSTALL/lib/libcurl.a" ]; then
  echo "ERROR: required cached builds are missing:"
  ls -la "$VULN_INSTALL/lib/libcurl.a" 2>/dev/null || true
  ls -la "$FIXED_INSTALL/lib/libcurl.a" 2>/dev/null || true
  echo "The repro stage should have built these in the project cache."
  exit 2
fi

cat > "$ARTIFACTS/variant_duphandle.c" <<'CEOF'
#include <curl/curl.h>
#include <stdio.h>

int main(void) {
  CURL *parent = curl_easy_init();
  CURL *child  = curl_easy_init();

  if(!parent || !child) {
    fprintf(stderr, "init failed\n");
    return 1;
  }

  curl_easy_setopt(parent, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0);
  curl_easy_setopt(child,  CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0);

  /* Set stream dependency of child on parent. */
  curl_easy_setopt(child, CURLOPT_STREAM_DEPENDS, parent);

  /* Duplicate the parent handle. The clone shares the priority-children
   * pointer from the original parent because dupset() copies the whole set
   * struct with a plain assignment. */
  CURL *parent_clone = curl_easy_duphandle(parent);
  if(!parent_clone) {
    fprintf(stderr, "duphandle failed\n");
    return 1;
  }

  /* Free the original child first. */
  curl_easy_cleanup(child);

  /* Cleanup the original parent, which frees the shared priority node. */
  curl_easy_cleanup(parent);

  /* Cleanup the cloned parent. It still references the freed priority node. */
  curl_easy_cleanup(parent_clone);

  return 0;
}
CEOF

cat > "$ARTIFACTS/variant_reset_parent.c" <<'CEOF'
#include <curl/curl.h>
#include <stdio.h>

int main(void) {
  CURL *parent = curl_easy_init();
  CURL *child  = curl_easy_init();

  if(!parent || !child) {
    fprintf(stderr, "init failed\n");
    return 1;
  }

  curl_easy_setopt(parent, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0);
  curl_easy_setopt(child,  CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2_0);

  /* Set stream dependency of child on parent. */
  curl_easy_setopt(child, CURLOPT_STREAM_DEPENDS, parent);

  /* Reset and cleanup the parent. This zeroes the parent's children list while
   * the child still holds a parent pointer. */
  curl_easy_reset(parent);
  curl_easy_cleanup(parent);

  /* Cleanup the child, which dereferences the freed parent. */
  curl_easy_cleanup(child);

  return 0;
}
CEOF

compile_and_run() {
  local role="$1"
  local install="$2"
  local src="$3"
  local out="$ARTIFACTS/reproducer_${role}"
  local log="$LOGS/reproducer_${role}.log"

  rm -f "$log" "$out"
  echo "[$role] Compiling $(basename "$src")..." >> "$log"
  if ! gcc -g -fsanitize=address \
       -I"$install/include" \
       "$src" \
       -L"$install/lib" -lcurl -lnghttp2 -lssl -lcrypto -lz \
       -o "$out" >>"$log" 2>&1; then
    echo "[$role] Compilation failed" >> "$log"
    echo "255" > "$ARTIFACTS/reproducer_${role}_exit.txt"
    return 255
  fi

  echo "[$role] Running reproducer..." >> "$log"
  set +e
  LD_LIBRARY_PATH="$install/lib" "$out" >>"$log" 2>&1
  local rc=$?
  set -e
  echo "$rc" > "$ARTIFACTS/reproducer_${role}_exit.txt"
  return $rc
}

check_uaf() {
  local log="$1"
  if grep -q "ERROR: AddressSanitizer: heap-use-after-free" "$log" 2>/dev/null; then
    echo "true"
  else
    echo "false"
  fi
}

RESULTS=()

for variant in duphandle reset_parent; do
  SRC="$ARTIFACTS/variant_${variant}.c"
  echo "=== Variant: $variant ==="

  echo "--- vulnerable ($VULN_COMMIT) ---"
  VULN_RC=0
  compile_and_run "vuln_${variant}" "$VULN_INSTALL" "$SRC" || VULN_RC=$?
  VULN_UAF=$(check_uaf "$LOGS/reproducer_vuln_${variant}.log")
  echo "VULN_${variant}_RC=$VULN_RC VULN_${variant}_UAF=$VULN_UAF"

  echo "--- fixed ($FIXED_COMMIT) ---"
  FIXED_RC=0
  compile_and_run "fixed_${variant}" "$FIXED_INSTALL" "$SRC" || FIXED_RC=$?
  FIXED_UAF=$(check_uaf "$LOGS/reproducer_fixed_${variant}.log")
  echo "FIXED_${variant}_RC=$FIXED_RC FIXED_${variant}_UAF=$FIXED_UAF"

  LATEST_UAF="false"
  LATEST_RC="skipped"
  if [ -f "$LATEST_INSTALL/lib/libcurl.a" ]; then
    echo "--- latest ($LATEST_TAG / $LATEST_COMMIT) ---"
    set +e
    compile_and_run "latest_${variant}" "$LATEST_INSTALL" "$SRC" || true
    LATEST_RC=$(cat "$ARTIFACTS/reproducer_latest_${variant}_exit.txt" 2>/dev/null || echo "unknown")
    set -e
    LATEST_UAF=$(check_uaf "$LOGS/reproducer_latest_${variant}.log")
    echo "LATEST_${variant}_RC=$LATEST_RC LATEST_${variant}_UAF=$LATEST_UAF"
  fi

  RESULTS+=("${variant}:${VULN_UAF}:${FIXED_UAF}:${LATEST_UAF}")
done

# A bypass would require the variant to trigger a UAF on the fixed or latest
# install. The curl fix removes the stream-dependency feature entirely, so the
# variant is only a different trigger on the vulnerable version.
BYPASS_FOUND=false
for r in "${RESULTS[@]}"; do
  IFS=':' read -r variant vuln fixed latest <<< "$r"
  if [ "$fixed" = "true" ] || [ "$latest" = "true" ]; then
    BYPASS_FOUND=true
  fi
done

echo "RESULTS=${RESULTS[*]}"

if [ "$BYPASS_FOUND" = "true" ]; then
  echo "=== RESULT: bypass found (variant reproduces on fixed/latest) ==="
  EXIT_CODE=0
else
  echo "=== RESULT: alternate trigger on vulnerable version only; no bypass ==="
  EXIT_CODE=1
fi

jq -n \
  --arg result "$([ "$BYPASS_FOUND" = true ] && echo confirmed_bypass || echo confirmed_variant_no_bypass)" \
  --arg vuln_commit "$VULN_COMMIT" \
  --arg fixed_commit "$FIXED_COMMIT" \
  --arg latest_commit "$LATEST_COMMIT" \
  --argjson results "$(printf '%s\n' "${RESULTS[@]}" | jq -R -s 'split("\n") | map(select(. != "") | split(":") | {variant:.[0], vuln_uaf:.[1], fixed_uaf:.[2], latest_uaf:.[3]})')" \
  '{
    entrypoint_kind: "function_call",
    entrypoint_detail: "curl_easy_duphandle (primary) and curl_easy_reset(parent) (secondary) after CURLOPT_STREAM_DEPENDS",
    service_started: false,
    healthcheck_passed: true,
    target_path_reached: true,
    runtime_stack: ["libcurl"],
    proof_artifacts: ["logs/vuln_variant/reproducer_vuln_duphandle.log", "logs/vuln_variant/reproducer_fixed_duphandle.log"],
    notes: "The duphandle variant triggers a heap-use-after-free in data_priority_cleanup on the vulnerable build, but exits cleanly on the fixed and latest builds because the dependency tree was removed.",
    result: $result,
    vulnerable_commit: $vuln_commit,
    fixed_commit: $fixed_commit,
    latest_commit: $latest_commit,
    variant_results: $results
  }' > "$VARIANT_DIR/runtime_manifest.json"

echo "Wrote $VARIANT_DIR/runtime_manifest.json"
exit "$EXIT_CODE"
