# REPRO-2026-00286: Log4j MapMessage emits invalid JSON for non-finite values ## Summary Status: published Severity: medium Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00286 CVE: CVE-2026-49844 ## Package Name: org.apache.logging.log4j:log4j-api Ecosystem: maven Affected: >=2.13.1,<2.25.5; >=2.26.0,<2.26.1; >=3.0.0-alpha1,<=3.0.0-beta2 Fixed: 2.25.5; 2.26.1 ## Root Cause # RCA Report — CVE-2026-49844 ## Summary CVE-2026-49844 is an improper encoding/serialization flaw in Apache Log4j API's `MapMessage` JSON output. When a `MapMessage` containing non-finite floating-point values (`NaN`, `Infinity`, `-Infinity`) is serialized to JSON via `MapMessage.asJson()` or `MapMessage.getFormattedMessage(new String[]{"JSON"})`, the `MapMessageJsonFormatter` emits the bare tokens `NaN`, `Infinity`, and `-Infinity` instead of an RFC 8259-compliant representation. These bare tokens are not valid JSON values, so any conformant downstream JSON parser or log-ingestion system will reject or fail to process the affected log records. This is a bypass of the earlier CVE-2026-34481 fix (which addressed `JsonTemplateLayout` but did not cover the `MapMessage.asJson()` code path in `log4j-api`). ## Impact - **Package/component affected:** `org.apache.logging.log4j:log4j-api` — class `org.apache.logging.log4j.message.MapMessageJsonFormatter`, reached via `MapMessage.asJson(StringBuilder)` and `MapMessage.getFormattedMessage(String[])` with the `"JSON"` format. - **Affected versions:** `log4j-api` 2.13.1 through 2.25.4, and 2.26.0. (Also `3.0.0-alpha1`+ per the Snyk advisory.) - **Fixed versions:** 2.25.5 and 2.26.1. - **Risk level:** Medium (CWE-116: Improper Encoding or Escaping of Output). - **Consequences:** Malformed JSON in log output. Downstream JSON parsers and log-ingestion/indexing pipelines that enforce RFC 8259 will reject or fail on affected records. An attacker who can influence a floating-point value logged in a `MapMessage` (e.g., via `JsonTemplateLayout`'s message resolver or any layout relying on `MapMessage.asJson()`) can inject non-finite values to corrupt log records or disrupt log processing. This is not a remote code execution issue. ## Impact Parity - **Disclosed/claimed maximum impact:** Improper encoding of non-finite floating-point values during `MapMessage` JSON serialization producing output that is not valid JSON (serialization/encoding flaw, `other`). Explicitly stated as not an RCE issue. - **Reproduced impact from this run:** All 13 test cases (NaN, +Infinity, -Infinity as scalar `Double`, `double[]`, scalar `Float`, `float[]`, plus a combined `asJson()` direct call) produce bare `NaN`/`Infinity`/`-Infinity` tokens in the vulnerable version. A strict RFC 8259 JSON parser (Gson `JsonReader` with `lenient=false`) rejects all 13 outputs. The fixed version quotes all values as JSON strings (`"NaN"`, `"Infinity"`, `"-Infinity"`) and all 13 parse successfully. - **Parity:** `full` — the reproduced behavior exactly matches the disclosed impact (invalid JSON from non-finite values; fix quotes them). - **Not demonstrated:** N/A — no code execution was claimed or expected. ## Root Cause In the vulnerable `MapMessageJsonFormatter.java` (log4j-api ≤ 2.25.4 / 2.26.0), the `formatNumber()` method handles `Double` and `Float` values by directly appending the primitive to the `StringBuilder`: ```java // VULNERABLE (rel/2.25.4) } else if (number instanceof Double) { final double doubleNumber = (Double) number; sb.append(doubleNumber); // <-- produces "NaN", "Infinity", "-Infinity" } else if (number instanceof Float) { final float floatNumber = (float) number; sb.append(floatNumber); // <-- produces "NaN", "Infinity", "-Infinity" } ``` Java's `StringBuilder.append(double)` delegates to `Double.toString(double)`, which returns the strings `"NaN"`, `"Infinity"`, and `"-Infinity"` for non-finite values. These are **bare literals** in the JSON output — they are not enclosed in quotes and are not valid JSON number syntax per RFC 8259 §6 (which only permits finite numbers). The same issue exists in `formatDoubleArray()` and `formatFloatArray()`, which also call `sb.append(item)` directly for array elements. The call chain is: ``` MapMessage.getFormattedMessage(new String[]{"JSON"}) → MapMessage.format(MapFormat.JSON, sb) → MapMessage.asJson(sb) [protected] → MapMessageJsonFormatter.format(sb, data) → formatNumber(sb, number) / formatDoubleArray(...) / formatFloatArray(...) ``` **Fix (PR #4163, merged as commit `19edb23`/squash `c7103d5` on `2.x`; `feadf8eb` on `2.25.x`; `1352b987` on `2.26.x`):** The fix adds two private helper methods that check `Double.isFinite()` / `Float.isFinite()` and, for non-finite values, call `formatString()` to wrap the value in JSON string quotes: ```java // FIXED (rel/2.25.5) private static void formatDouble(StringBuilder sb, double doubleNumber) { if (!Double.isFinite(doubleNumber)) { formatString(sb, Double.toString(doubleNumber)); // quotes: "NaN", "Infinity", etc. } else { sb.append(doubleNumber); } } ``` All `sb.append(doubleNumber)` / `sb.append(floatNumber)` / `sb.append(item)` call sites in `formatNumber()`, `formatDoubleArray()`, and `formatFloatArray()` are replaced with calls to `formatDouble()` / `formatFloat()`. ## Reproduction Steps 1. **Script:** `bundle/repro/reproduction_steps.sh` (self-contained, idempotent). 2. **What the script does:** - Installs OpenJDK 17 if not present. - Downloads the official Apache-published `log4j-api` artifacts from Maven Central: `log4j-api-2.25.4.jar` (vulnerable, built from tag `rel/2.25.4`) and `log4j-api-2.25.5.jar` (fixed, built from tag `rel/2.25.5`). Also downloads `gson-2.11.0.jar` for strict JSON validation. - Verifies via `javap` that the vulnerable jar lacks `formatDouble()`/`formatFloat()` helper methods and the fixed jar has them. - Compiles `bundle/repro/NonFiniteJsonTest.java` — a Java harness that creates a concrete `MapMessage` subclass and exercises both `getFormattedMessage(new String[]{"JSON"})` (public API) and `asJson(StringBuilder)` (protected, via subclass) with `NaN`, `+Infinity`, `-Infinity` as scalar `double`/`float` and as `double[]`/`float[]` array elements (13 total cases). - Runs the harness against each jar. The harness detects bare (unquoted) non-finite tokens via a character-level scanner and validates each output with a strict RFC 8259 JSON parser (Gson `JsonReader` with `setLenient(false)` + `skipValue()`). - Emits a machine-readable `VERDICT|...` line and writes `bundle/repro/runtime_manifest.json`. 3. **Expected evidence of reproduction:** - Vulnerable 2.25.4: `bare=13 quoted=0 parseFail=13` — all outputs contain bare `NaN`/`Infinity`/`-Infinity` and fail strict JSON parsing. - Fixed 2.25.5: `bare=0 quoted=13 parseFail=0` — all outputs quote the values as JSON strings and parse successfully. ## Evidence - **Log files:** - `bundle/logs/vulnerable_output.log` — full harness output for vulnerable log4j-api 2.25.4 - `bundle/logs/fixed_output.log` — full harness output for fixed log4j-api 2.25.5 - `bundle/logs/compile.log` — javac compilation output - **Key excerpts (vulnerable 2.25.4):** ``` JSON output: {"number":NaN} >> BARE non-finite token detected (invalid JSON per RFC 8259) >> strict JSON parse FAILED (invalid per RFC 8259) JSON output: {"numbers":[-Infinity]} >> BARE non-finite token detected (invalid JSON per RFC 8259) >> strict JSON parse FAILED (invalid per RFC 8259) SUMMARY: bare=13, quoted=0, parseFail=13 VERDICT|2.25.4-vulnerable|VULNERABLE|bare=13|quoted=0|parseFail=13 ``` - **Key excerpts (fixed 2.25.5):** ``` JSON output: {"number":"NaN"} >> non-finite value is QUOTED (valid JSON string) >> strict JSON parse OK (valid RFC 8259 JSON) JSON output: {"numbers":["-Infinity"]} >> non-finite value is QUOTED (valid JSON string) >> strict JSON parse OK (valid RFC 8259 JSON) SUMMARY: bare=0, quoted=13, parseFail=0 VERDICT|2.25.5-fixed|FIXED|bare=0|quoted=13|parseFail=0 ``` - **javap verification:** - Vulnerable 2.25.4 `MapMessageJsonFormatter` methods: no `formatDouble(`, no `formatFloat(`. - Fixed 2.25.5 `MapMessageJsonFormatter` methods: has `formatDouble(StringBuilder, double)` and `formatFloat(StringBuilder, float)`. - **Environment:** OpenJDK 17.0.19, Gson 2.11.0 (strict JSON validator), official Apache log4j-api jars from Maven Central (verified identical to source-built jar from `rel/2.25.4` tag — same file size 351127 bytes). - **Runtime manifest:** `bundle/repro/runtime_manifest.json` ## Recommendations / Next Steps - **Upgrade guidance:** Upgrade `log4j-api` to version 2.25.5+ (2.25.x line) or 2.26.1+ (2.26.x line). If on 3.0.0-alpha, monitor for a fix. - **Suggested fix approach:** Already implemented in PR #4163 — gate non-finite `Double`/`Float` values through `formatString()` so they are emitted as JSON strings. This mirrors Jackson's `JsonWriteFeature#WRITE_NAN_AS_STRINGS` behavior. - **Testing recommendations:** The fix includes regression tests (`MapMessageTest.testJsonFormatterDoubleNonFiniteSupport` and `testJsonFormatterFloatNonFiniteSupport`) that parameterize over `NaN`, `+Infinity`, `-Infinity` for both `double` and `float`. Any future changes to `MapMessageJsonFormatter` should ensure these tests continue to pass. Consider adding strict-JSON-parser validation to CI for all `MapMessage` JSON output. - **Downstream mitigation:** Log-ingestion systems that use lenient JSON parsers (e.g., Gson default mode, some JavaScript engines) may silently accept bare `NaN`/`Infinity`, but strict parsers (RFC 8259) will reject them. Validate log JSON output with a strict parser. ## Additional Notes - **Idempotency confirmation:** The script was run twice consecutively; both runs produced identical results (exit code 0, `bare=13` for vulnerable, `bare=0` for fixed). The script skips re-downloading jars if they already exist. - **Source build verification:** In addition to the Maven Central artifacts, the `log4j-api` module was built from source at tag `rel/2.25.4` using Maven (`mvn -pl log4j-api-java9,log4j-api -am install -DskipTests`). The source-built jar (`log4j-api-2.25.4.jar`, 351127 bytes) is byte-size-identical to the Maven Central artifact, confirming the published jar matches the repository source. - **Edge cases covered:** The harness tests both scalar and array forms for both `Double` and `Float`, covering all code paths in `formatNumber()`, `formatDoubleArray()`, and `formatFloatArray()`. The `BigDecimal` path is unaffected (it already uses `toString()` which never produces non-finite tokens). - **Scope:** This is a `library_api` claim (`claimed_surface=library_api`, `required_entrypoint_kind=function_call`). The reproduction exercises the real `MapMessage.getFormattedMessage(["JSON"])` and `MapMessage.asJson()` functions from the official Apache `log4j-api` artifacts. No service/network boundary is involved. ## Reproduction Details Reproduced: 2026-07-13T18:47:46.937Z Duration: 674 seconds Tool calls: 170 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00286 pruva-verify CVE-2026-49844 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00286&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00286/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-49844 - Source: https://logging.apache.org/security.html#CVE-2026-49844 ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 10616 bytes) - bundle/repro/rca_report.md (analysis, 10702 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 11445 bytes) - bundle/vuln_variant/rca_report.md (analysis, 17420 bytes) - bundle/artifact_promotion_manifest.json (other, 8725 bytes) - bundle/artifact_promotion_report.json (other, 8743 bytes) - bundle/vuln_variant/VariantTest.java (other, 12996 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 2571 bytes) - bundle/vuln_variant/source_identity.json (other, 2615 bytes) - bundle/logs/vulnerable_output.log (log, 3331 bytes) - bundle/logs/fixed_output.log (log, 3118 bytes) - bundle/repro/runtime_manifest.json (other, 771 bytes) - bundle/repro/NonFiniteJsonTest.java (other, 8809 bytes) - bundle/repro/validation_verdict.json (other, 972 bytes) - bundle/logs/compile.log (log, 185 bytes) - bundle/vuln_variant/variant_manifest.json (other, 4601 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3208 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1437 bytes) - bundle/logs/vuln_variant/variant_2.26.1.log (log, 4209 bytes) - bundle/logs/vuln_variant/variant_2.25.4.log (log, 4486 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 8539 bytes) - bundle/logs/vuln_variant/variant_2.25.5.log (log, 4209 bytes) - bundle/logs/vuln_variant/variant_2.26.0.log (log, 4486 bytes) - bundle/logs/vuln_variant/compile.log (log, 186 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00286 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00286/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00286 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev