#!/bin/bash
set -euo pipefail

# =============================================================================
# CVE-2026-49844 — Apache Log4j MapMessage JSON non-finite number serialization
# =============================================================================
# Reproduces the bug where MapMessage.asJson() / getFormattedMessage(["JSON"])
# emits bare NaN / Infinity / -Infinity tokens (invalid per RFC 8259) when a
# logged map contains non-finite floating-point values.
#
# Approach:
#   Uses the official Apache-published log4j-api artifacts from Maven Central,
#   which are built from the exact release tags:
#     - rel/2.25.4  (vulnerable, last affected release in the 2.25.x line)
#     - rel/2.25.5  (fixed, first release containing the fix from PR #4163)
#   A Java harness (NonFiniteJsonTest.java) calls the real
#   MapMessage.getFormattedMessage(["JSON"]) and MapMessage.asJson() methods
#   with NaN / +Infinity / -Infinity values for both Double and Float, as
#   scalars and as array elements. Output is validated with a strict
#   (RFC 8259) JSON parser (Gson JsonReader, lenient=false).
#
# Positive control (vulnerable 2.25.4): bare tokens emitted, JSON parse fails.
# Negative control (fixed 2.25.5):     values quoted, JSON parse succeeds.
# =============================================================================

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

cd "$ROOT"

JAR_DIR="$ARTIFACTS/jars"
mkdir -p "$JAR_DIR"

VULN_VERSION="2.25.4"
FIXED_VERSION="2.25.5"
GSON_VERSION="2.11.0"

VULN_JAR="$JAR_DIR/log4j-api-${VULN_VERSION}.jar"
FIXED_JAR="$JAR_DIR/log4j-api-${FIXED_VERSION}.jar"
GSON_JAR="$JAR_DIR/gson-${GSON_VERSION}.jar"

VULN_LOG="$LOGS/vulnerable_output.log"
FIXED_LOG="$LOGS/fixed_output.log"
COMPILE_LOG="$LOGS/compile.log"

echo "[*] CVE-2026-49844 reproduction starting"
echo "[*] Vulnerable version : log4j-api ${VULN_VERSION} (rel/${VULN_VERSION})"
echo "[*] Fixed version      : log4j-api ${FIXED_VERSION} (rel/${FIXED_VERSION})"

# -----------------------------------------------------------------------------
# Step 1: Install JDK (clean sandbox has no Java)
# -----------------------------------------------------------------------------
if ! command -v javac >/dev/null 2>&1; then
    echo "[*] Installing OpenJDK 17..."
    sudo apt-get update -qq && sudo apt-get install -y -qq openjdk-17-jdk-headless >/dev/null 2>&1
fi
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
export JAVA_HOME
echo "[*] Java: $(java -version 2>&1 | head -1)"

# -----------------------------------------------------------------------------
# Step 2: Download official Apache log4j-api artifacts from Maven Central
#         (these are the real compiled code from the rel/2.25.4 and rel/2.25.5 tags)
# -----------------------------------------------------------------------------
download_jar() {
    local url="$1" dest="$2" name="$3"
    if [ -f "$dest" ]; then
        echo "[*] $name already present: $dest"
    else
        echo "[*] Downloading $name from $url"
        curl -fsSL -o "$dest" "$url"
    fi
}

MAVEN_BASE="https://repo1.maven.org/maven2"
download_jar \
    "${MAVEN_BASE}/org/apache/logging/log4j/log4j-api/${VULN_VERSION}/log4j-api-${VULN_VERSION}.jar" \
    "$VULN_JAR" "log4j-api ${VULN_VERSION} (vulnerable)"
download_jar \
    "${MAVEN_BASE}/org/apache/logging/log4j/log4j-api/${FIXED_VERSION}/log4j-api-${FIXED_VERSION}.jar" \
    "$FIXED_JAR" "log4j-api ${FIXED_VERSION} (fixed)"
download_jar \
    "${MAVEN_BASE}/com/google/code/gson/gson/${GSON_VERSION}/gson-${GSON_VERSION}.jar" \
    "$GSON_JAR" "gson ${GSON_VERSION} (strict JSON validator)"

# Verify jars are valid and contain the target class
for jar in "$VULN_JAR" "$FIXED_JAR"; do
    if ! jar tf "$jar" 2>/dev/null | grep -q "MapMessageJsonFormatter.class"; then
        echo "[!] ERROR: $jar does not contain MapMessageJsonFormatter.class"
        exit 1
    fi
done
echo "[*] All jars verified (MapMessageJsonFormatter.class present)"

# -----------------------------------------------------------------------------
# Step 3: Verify the vulnerable jar LACKS the fix and the fixed jar HAS it
#         (the fix added formatDouble/formatFloat private helper methods)
# -----------------------------------------------------------------------------
echo ""
echo "[*] Verifying fix presence via javap method signatures..."
VULN_METHODS=$(javap -p -classpath "$VULN_JAR" org.apache.logging.log4j.message.MapMessageJsonFormatter 2>&1)
FIXED_METHODS=$(javap -p -classpath "$FIXED_JAR" org.apache.logging.log4j.message.MapMessageJsonFormatter 2>&1)

if echo "$VULN_METHODS" | grep -q "formatDouble("; then
    echo "[!] UNEXPECTED: vulnerable jar has formatDouble method (should be absent)"
    exit 1
fi
if ! echo "$FIXED_METHODS" | grep -q "formatDouble("; then
    echo "[!] UNEXPECTED: fixed jar lacks formatDouble method (should be present)"
    exit 1
fi
echo "[*] OK: vulnerable ${VULN_VERSION} lacks formatDouble/formatFloat; fixed ${FIXED_VERSION} has them"

# -----------------------------------------------------------------------------
# Step 4: Compile the Java harness
# -----------------------------------------------------------------------------
echo ""
echo "[*] Compiling NonFiniteJsonTest.java..."
javac -d "$REPRO_DIR" -cp "${VULN_JAR}:${GSON_JAR}" "$REPRO_DIR/NonFiniteJsonTest.java" 2>&1 | tee "$COMPILE_LOG" || {
    echo "[!] Compilation failed"
    exit 1
}
echo "[*] Compilation successful"

# -----------------------------------------------------------------------------
# Step 5: Run harness against VULNERABLE version (positive control)
# -----------------------------------------------------------------------------
echo ""
echo "========================================"
echo "[*] Running against VULNERABLE log4j-api ${VULN_VERSION}"
echo "========================================"
java -cp "${VULN_JAR}:${GSON_JAR}:${REPRO_DIR}" NonFiniteJsonTest "${VULN_VERSION}-vulnerable" 2>&1 | tee "$VULN_LOG"

VULN_VERDICT=$(grep "^VERDICT|" "$VULN_LOG" | tail -1)
echo "[*] Vulnerable verdict: $VULN_VERDICT"

# -----------------------------------------------------------------------------
# Step 6: Run harness against FIXED version (negative control)
# -----------------------------------------------------------------------------
echo ""
echo "========================================"
echo "[*] Running against FIXED log4j-api ${FIXED_VERSION}"
echo "========================================"
java -cp "${FIXED_JAR}:${GSON_JAR}:${REPRO_DIR}" NonFiniteJsonTest "${FIXED_VERSION}-fixed" 2>&1 | tee "$FIXED_LOG"

FIXED_VERDICT=$(grep "^VERDICT|" "$FIXED_LOG" | tail -1)
echo "[*] Fixed verdict: $FIXED_VERDICT"

# -----------------------------------------------------------------------------
# Step 7: Parse verdicts and determine outcome
# -----------------------------------------------------------------------------
VULN_BARE=$(echo "$VULN_VERDICT" | sed -n 's/.*|bare=\([0-9]*\).*/\1/p')
VULN_QUOTED=$(echo "$VULN_VERDICT" | sed -n 's/.*|quoted=\([0-9]*\).*/\1/p')
VULN_PARSEFAIL=$(echo "$VULN_VERDICT" | sed -n 's/.*|parseFail=\([0-9]*\).*/\1/p')
FIXED_BARE=$(echo "$FIXED_VERDICT" | sed -n 's/.*|bare=\([0-9]*\).*/\1/p')
FIXED_QUOTED=$(echo "$FIXED_VERDICT" | sed -n 's/.*|quoted=\([0-9]*\).*/\1/p')
FIXED_PARSEFAIL=$(echo "$FIXED_VERDICT" | sed -n 's/.*|parseFail=\([0-9]*\).*/\1/p')

echo ""
echo "========================================"
echo "[*] RESULT ANALYSIS"
echo "========================================"
echo "  Vulnerable ${VULN_VERSION}: bare=${VULN_BARE} quoted=${VULN_QUOTED} parseFail=${VULN_PARSEFAIL}"
echo "  Fixed      ${FIXED_VERSION}: bare=${FIXED_BARE} quoted=${FIXED_QUOTED} parseFail=${FIXED_PARSEFAIL}"

CONFIRMED=false
if [ "${VULN_BARE:-0}" -gt 0 ] && [ "${VULN_PARSEFAIL:-0}" -gt 0 ] && \
   [ "${FIXED_BARE:-0}" -eq 0 ] && [ "${FIXED_PARSEFAIL:-0}" -eq 0 ]; then
    CONFIRMED=true
    echo ""
    echo "[+] CONFIRMED: CVE-2026-49844 reproduced."
    echo "[+] Vulnerable ${VULN_VERSION} emits bare NaN/Infinity/-Infinity tokens (invalid JSON, RFC 8259 violations)."
    echo "[+] Fixed ${FIXED_VERSION} quotes non-finite values as JSON strings (valid JSON)."
else
    echo ""
    echo "[-] NOT CONFIRMED: expected vulnerable bare>0 & parseFail>0, fixed bare=0 & parseFail=0."
fi

# -----------------------------------------------------------------------------
# Step 8: Write runtime manifest (required deliverable)
# -----------------------------------------------------------------------------
# Build proof_artifacts list from actual files
PROOF_ARTIFACTS="[]"
if [ -f "$VULN_LOG" ]; then
    PROOF_ARTIFACTS=$(echo "$PROOF_ARTIFACTS" | jq --arg p "logs/vulnerable_output.log" '. + [$p]')
fi
if [ -f "$FIXED_LOG" ]; then
    PROOF_ARTIFACTS=$(echo "$PROOF_ARTIFACTS" | jq --arg p "logs/fixed_output.log" '. + [$p]')
fi
if [ -f "$COMPILE_LOG" ]; then
    PROOF_ARTIFACTS=$(echo "$PROOF_ARTIFACTS" | jq --arg p "logs/compile.log" '. + [$p]')
fi

CONFIRMED_JSON="false"
if [ "$CONFIRMED" = true ]; then
    CONFIRMED_JSON="true"
fi

cat > "$REPRO_DIR/runtime_manifest.json" <<JSON
{
  "entrypoint_kind": "function_call",
  "entrypoint_detail": "MapMessage.getFormattedMessage([\"JSON\"]) and MapMessage.asJson(StringBuilder) in log4j-api org.apache.logging.log4j.message.MapMessageJsonFormatter",
  "service_started": false,
  "healthcheck_passed": false,
  "target_path_reached": true,
  "runtime_stack": ["openjdk-17", "log4j-api-${VULN_VERSION}-vulnerable", "log4j-api-${FIXED_VERSION}-fixed", "gson-${GSON_VERSION}"],
  "proof_artifacts": ${PROOF_ARTIFACTS},
  "notes": "Harness exercises real MapMessage JSON serialization with NaN/Infinity/-Infinity. Vulnerable ${VULN_VERSION} emits ${VULN_BARE} bare non-finite tokens (${VULN_PARSEFAIL} strict JSON parse failures). Fixed ${FIXED_VERSION} emits ${FIXED_QUOTED} quoted values (0 parse failures). Confirmed=${CONFIRMED_JSON}."
}
JSON

echo ""
echo "[*] Runtime manifest written to $REPRO_DIR/runtime_manifest.json"
cat "$REPRO_DIR/runtime_manifest.json"

# -----------------------------------------------------------------------------
# Exit code: 0 = issue confirmed, 1 = not reproduced
# -----------------------------------------------------------------------------
if [ "$CONFIRMED" = true ]; then
    echo ""
    echo "[+] Reproduction complete: CVE-2026-49844 CONFIRMED."
    exit 0
else
    echo ""
    echo "[-] Reproduction did not confirm the issue."
    exit 1
fi
