#!/bin/bash
set -euo pipefail

# =============================================================================
# CVE-2026-49844 — VARIANT / BYPASS analysis
# =============================================================================
# Tests whether the PR #4163 fix (formatDouble/formatFloat gating in
# MapMessageJsonFormatter) is COMPLETE across the *alternate* data paths that
# the original repro (NonFiniteJsonTest) did NOT cover:
#
#   A. nested java.util.Map value            -> formatMap  -> formatNumber
#   B. java.util.List value                  -> formatList -> formatNumber
#   C. Object[] value                        -> formatObjectArray -> formatNumber
#   D. non-List Collection (Set/Deque)       -> formatCollection -> formatNumber
#   E. custom Number subclass (else branch)  -> formatNumber else-branch
#   F. BigInteger overflow -> Infinity       -> formatNumber else-branch
#      (BigInteger is NOT special-cased in MapMessageJsonFormatter, unlike
#       JsonWriter which has an instanceof BigInteger branch.)
#   G. StringBuilderFormattable emitting NaN -> formatFormattable (control)
#
# Versions tested (official Apache log4j-api jars from Maven Central):
#   - 2.25.4  (vulnerable, 2.25.x line, last affected)
#   - 2.25.5  (FIXED,     2.25.x line)
#   - 2.26.0  (vulnerable, 2.26.x line, affected)
#   - 2.26.1  (FIXED,     2.26.x line)
#
# Exit codes:
#   0 = BYPASS confirmed: a FIXED version still emits a bare non-finite token
#       on at least one alternate data path.
#   1 = No bypass: all FIXED versions quote every alternate path.
#       (Alternate triggers may still reproduce on the vulnerable versions.)
# =============================================================================

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

cd "$ROOT"

JAR_DIR="$ARTIFACTS/jars"
GSON_VERSION="2.11.0"
GSON_JAR="$JAR_DIR/gson-${GSON_VERSION}.jar"

# version -> jar path + label
declare -a VERSIONS=("2.25.4:vulnerable-2.25.x" "2.25.5:fixed-2.25.x" "2.26.0:vulnerable-2.26.x" "2.26.1:fixed-2.26.x")

echo "[*] CVE-2026-49844 variant/bypass analysis starting"
echo "[*] Testing alternate data paths into MapMessageJsonFormatter"

# -----------------------------------------------------------------------------
# Step 1: JDK
# -----------------------------------------------------------------------------
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 jars (reuse existing; add 2.26.0 / 2.26.1)
# -----------------------------------------------------------------------------
download_jar() {
    local url="$1" dest="$2" name="$3"
    if [ -f "$dest" ]; then
        echo "[*] $name already present: $dest"
    else
        echo "[*] Downloading $name"
        curl -fsSL -o "$dest" "$url"
    fi
}

MAVEN_BASE="https://repo1.maven.org/maven2"
download_jar "${MAVEN_BASE}/com/google/code/gson/gson/${GSON_VERSION}/gson-${GSON_VERSION}.jar" "$GSON_JAR" "gson ${GSON_VERSION}"

for entry in "${VERSIONS[@]}"; do
    ver="${entry%%:*}"
    jar="$JAR_DIR/log4j-api-${ver}.jar"
    download_jar "${MAVEN_BASE}/org/apache/logging/log4j/log4j-api/${ver}/log4j-api-${ver}.jar" "$jar" "log4j-api ${ver}"
    if ! jar tf "$jar" 2>/dev/null | grep -q "MapMessageJsonFormatter.class"; then
        echo "[!] ERROR: $jar missing MapMessageJsonFormatter.class"
        exit 1
    fi
done
echo "[*] All jars present and verified"

# -----------------------------------------------------------------------------
# Step 3: Verify fix presence (formatDouble/formatFloat) per version via javap
# -----------------------------------------------------------------------------
echo ""
echo "[*] Fix presence check (formatDouble/formatFloat helper methods):"
for entry in "${VERSIONS[@]}"; do
    ver="${entry%%:*}"
    label="${entry##*:}"
    jar="$JAR_DIR/log4j-api-${ver}.jar"
    methods=$(javap -p -classpath "$jar" org.apache.logging.log4j.message.MapMessageJsonFormatter 2>&1)
    if echo "$methods" | grep -q "formatDouble(java.lang.StringBuilder, double)"; then
        echo "    log4j-api ${ver} (${label}): FIX PRESENT (formatDouble/formatFloat)"
    else
        echo "    log4j-api ${ver} (${label}): fix ABSENT (vulnerable)"
    fi
done

# -----------------------------------------------------------------------------
# Step 4: Compile the variant harness
# -----------------------------------------------------------------------------
echo ""
echo "[*] Compiling VariantTest.java..."
VULN_JAR="$JAR_DIR/log4j-api-2.25.4.jar"
javac -d "$VDIR" -cp "${VULN_JAR}:${GSON_JAR}" "$VDIR/VariantTest.java" 2>&1 | tee "$LOGS/compile.log"
echo "[*] Compilation successful"

# -----------------------------------------------------------------------------
# Step 5: Run harness against every version
# -----------------------------------------------------------------------------
declare -A VERDICT_BARE
declare -A VERDICT_QUOTED
declare -A VERDICT_PARSEFAIL
declare -A VERDICT_STATUS

for entry in "${VERSIONS[@]}"; do
    ver="${entry%%:*}"
    label="${entry##*:}"
    jar="$JAR_DIR/log4j-api-${ver}.jar"
    log="$LOGS/variant_${ver}.log"
    echo ""
    echo "========================================"
    echo "[*] Running variant harness against log4j-api ${ver} (${label})"
    echo "========================================"
    java -cp "${jar}:${GSON_JAR}:${VDIR}" VariantTest "${ver}-${label}" 2>&1 | tee "$log"
    verdict=$(grep "^VERDICT|" "$log" | tail -1)
    echo "[*] Verdict: $verdict"
    bare=$(echo "$verdict" | sed -n 's/.*|bare=\([0-9]*\).*/\1/p')
    quoted=$(echo "$verdict" | sed -n 's/.*|quoted=\([0-9]*\).*/\1/p')
    parsefail=$(echo "$verdict" | sed -n 's/.*|parseFail=\([0-9]*\).*/\1/p')
    status=$(echo "$verdict" | sed -n 's/.*|\([A-Z]*\)|bare=.*/\1/p')
    VERDICT_BARE[$ver]=${bare:-0}
    VERDICT_QUOTED[$ver]=${quoted:-0}
    VERDICT_PARSEFAIL[$ver]=${parsefail:-0}
    VERDICT_STATUS[$ver]=${status:-UNKNOWN}
done

# -----------------------------------------------------------------------------
# Step 6: Determine bypass verdict
# -----------------------------------------------------------------------------
echo ""
echo "========================================"
echo "[*] VARIANT / BYPASS RESULT ANALYSIS"
echo "========================================"
for entry in "${VERSIONS[@]}"; do
    ver="${entry%%:*}"
    label="${entry##*:}"
    echo "  log4j-api ${ver} (${label}): status=${VERDICT_STATUS[$ver]} bare=${VERDICT_BARE[$ver]} quoted=${VERDICT_QUOTED[$ver]} parseFail=${VERDICT_PARSEFAIL[$ver]}"
done

# A bypass = a FIXED version (2.25.5 or 2.26.1) still emits a bare token.
BYPASS=false
for fver in "2.25.5" "2.26.1"; do
    if [ "${VERDICT_BARE[$fver]:-0}" -gt 0 ]; then
        BYPASS=true
        echo "[!] FIXED log4j-api ${fver} still emits bare non-finite tokens on alternate paths -> BYPASS"
    fi
done

# Confirm alternate triggers reproduce on vulnerable versions.
VULN_REPRO=false
for vver in "2.25.4" "2.26.0"; do
    if [ "${VERDICT_BARE[$vver]:-0}" -gt 0 ]; then
        VULN_REPRO=true
    fi
done

if [ "$BYPASS" = true ]; then
    echo ""
    echo "[+] BYPASS CONFIRMED: the fix does NOT cover all alternate data paths."
    OUTCOME="bypass_confirmed"
else
    echo ""
    if [ "$VULN_REPRO" = true ]; then
        echo "[-] NO BYPASS: all FIXED versions (2.25.5, 2.26.1) quote every alternate data path."
        echo "    (Alternate triggers DO reproduce on vulnerable 2.25.4 / 2.26.0, confirming"
        echo "     the bug has multiple data-path entry points, but the fix covers them all.)"
        OUTCOME="no_bypass_alternate_triggers_on_vulnerable"
    else
        echo "[-] NO VARIANT: alternate data paths did not even reproduce on vulnerable versions."
        OUTCOME="no_variant"
    fi
fi

# -----------------------------------------------------------------------------
# Step 7: Write runtime manifest
# -----------------------------------------------------------------------------
PROOF_ARTIFACTS="["
first=1
for entry in "${VERSIONS[@]}"; do
    ver="${entry%%:*}"
    logpath="logs/vuln_variant/variant_${ver}.log"
    if [ $first -eq 1 ]; then first=0; else PROOF_ARTIFACTS+=","; fi
    PROOF_ARTIFACTS+="\"$logpath\""
done
PROOF_ARTIFACTS+=",\"logs/vuln_variant/compile.log\"]"

BYPASS_JSON="false"; [ "$BYPASS" = true ] && BYPASS_JSON="true"

cat > "$VDIR/runtime_manifest.json" <<JSON
{
  "entrypoint_kind": "function_call",
  "entrypoint_detail": "Alternate data paths (nested Map, List, Object[], non-List Collection, custom Number, BigInteger-overflow, StringBuilderFormattable) into MapMessage.getFormattedMessage([\"JSON\"]) -> MapMessageJsonFormatter.format",
  "service_started": false,
  "healthcheck_passed": false,
  "target_path_reached": true,
  "runtime_stack": ["openjdk-17", "log4j-api-2.25.4-vulnerable", "log4j-api-2.25.5-fixed", "log4j-api-2.26.0-vulnerable", "log4j-api-2.26.1-fixed", "gson-2.11.0"],
  "proof_artifacts": ${PROOF_ARTIFACTS},
  "variant_outcome": "${OUTCOME}",
  "bypass_confirmed": ${BYPASS_JSON},
  "version_results": {
    "2.25.4": {"status": "${VERDICT_STATUS[2.25.4]}", "bare": ${VERDICT_BARE[2.25.4]}, "quoted": ${VERDICT_QUOTED[2.25.4]}, "parseFail": ${VERDICT_PARSEFAIL[2.25.4]}},
    "2.25.5": {"status": "${VERDICT_STATUS[2.25.5]}", "bare": ${VERDICT_BARE[2.25.5]}, "quoted": ${VERDICT_QUOTED[2.25.5]}, "parseFail": ${VERDICT_PARSEFAIL[2.25.5]}},
    "2.26.0": {"status": "${VERDICT_STATUS[2.26.0]}", "bare": ${VERDICT_BARE[2.26.0]}, "quoted": ${VERDICT_QUOTED[2.26.0]}, "parseFail": ${VERDICT_PARSEFAIL[2.26.0]}},
    "2.26.1": {"status": "${VERDICT_STATUS[2.26.1]}", "bare": ${VERDICT_BARE[2.26.1]}, "quoted": ${VERDICT_QUOTED[2.26.1]}, "parseFail": ${VERDICT_PARSEFAIL[2.26.1]}}
  },
  "notes": "Variant harness exercises alternate data paths into MapMessageJsonFormatter not covered by the original repro. BYPASS=${BYPASS_JSON}: a fixed version (2.25.5/2.26.1) still emitting bare non-finite tokens on any alternate path would be a bypass."
}
JSON
echo ""
echo "[*] Runtime manifest written to $VDIR/runtime_manifest.json"

# -----------------------------------------------------------------------------
# Step 8: Write a latest_version.txt recording the highest tested fixed ref
# -----------------------------------------------------------------------------
{
    echo "fixed_version_2.25.x=2.25.5"
    echo "fixed_version_2.26.x=2.26.1"
    echo "highest_tested_fixed=2.26.1"
} > "$LOGS/latest_version.txt"
echo "[*] latest_version.txt written to $LOGS/latest_version.txt"

# -----------------------------------------------------------------------------
# Exit code: 0 = bypass confirmed, 1 = no bypass
# -----------------------------------------------------------------------------
if [ "$BYPASS" = true ]; then
    echo ""
    echo "[+] Variant analysis complete: BYPASS CONFIRMED."
    exit 0
else
    echo ""
    echo "[-] Variant analysis complete: NO BYPASS (fix covers all alternate data paths)."
    exit 1
fi
