Bug Description
make_float() in FloatTraits.hpp has an unbounded loop index that reads past the end of the powersOfTen array when the absolute value of the exponent exceeds the array size (9 elements for double, 6 for float).
The bug is reachable through normal deserialization when a JSON value contains a decimal number with hundreds or thousands of fractional digits (e.g., "0.000...0001" with 9000+ zeros). The large number of decimal digits produces a huge negative exponent_offset in parseNumber(). While parseNumber() clamps exponents that exceed exponent_max in the positive direction, the negative direction is unclamped — the final exponent += exponent_offset can produce values like -9000, which passes through to make_float() where -e becomes 9000, causing the loop to iterate far beyond the array bounds.
Minimal Reproducer
#include <ArduinoJson.h>
#include <cstdio>
int main() {
// 500+ fractional digits creates exponent_offset < -500,
// exceeding the 9-element powersOfTen array (max safe |e| = 511)
const char* input = "[\"0."
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000"
"1\"]";
JsonDocument doc;
deserializeJson(doc, input);
// Triggers parseNumber() -> make_float() on the string value
int val = doc[0].as<int>();
printf("val = %d\n", val);
return 0;
}
Compile with AddressSanitizer to observe the crash:
clang++ -std=c++17 -fsanitize=address -I src/ repro.cpp -o repro && ./repro
ASAN output (abbreviated):
ERROR: AddressSanitizer: global-buffer-overflow
READ of size 8 at 0x... in make_float<double, long>() FloatTraits.hpp:206
Root Cause Analysis
In src/ArduinoJson/Numbers/FloatTraits.hpp, make_float():
template <typename TFloat, typename TExponent>
inline TFloat make_float(TFloat m, TExponent e) {
// ...
for (uint8_t index = 0; e != 0; index++) { // index unbounded
if (e & 1)
m *= powersOfTen[index]; // OOB when index >= array size
e >>= 1;
}
return m;
}
The positiveBinaryPowersOfTen() / negativeBinaryPowersOfTen() arrays have 9 elements (double) / 6 elements (float). When |e| > 511 (double) or |e| > 63 (float), the loop index exceeds the array bounds.
The caller parseNumber() in parseNumber.hpp has an early-return guard:
if (exponent + exponent_offset > traits::exponent_max) { ... }
But this only catches the positive direction. The negative direction (large exponent_offset from many fractional digits) is not clamped, allowing values like e = -9000 to reach make_float().
Suggested Fix
Either clamp e before entering make_float, or add a defensive bounds check inside the loop:
for (uint8_t index = 0; e != 0; index++) {
if (index >= traits::kMaxPowersOfTenIndex) // 9 for double, 6 for float
return e > 0 ? traits::inf() : TFloat(0);
if (e & 1)
m *= powersOfTen[index];
e >>= 1;
}
Alternatively, add a symmetric negative clamp in parseNumber():
if (-(exponent + exponent_offset) > traits::exponent_max) {
return Number(is_negative ? -0.0f : 0.0f);
}
Environment
- ArduinoJson version: 7.4.2
- Found via: AFL++ fuzzing with AddressSanitizer (
-fsanitize=address,undefined)
- Platform: ESP32-S3 (Arduino Core 3.x / ESP-IDF 5.x) — bug is platform-independent
- Troubleshooter: N/A — this is a parser bug, not a configuration issue
Impact
On hosted platforms (Linux, macOS) with ASAN, this crashes. Without ASAN, it reads adjacent memory and returns garbage float values. On embedded targets (AVR with PROGMEM, ESP32), it reads past the flash/global data section into adjacent memory. The bug is reachable from any untrusted JSON input containing a string or number with hundreds of fractional digits.
Bug Description
make_float()inFloatTraits.hpphas an unbounded loop index that reads past the end of thepowersOfTenarray when the absolute value of the exponent exceeds the array size (9 elements fordouble, 6 forfloat).The bug is reachable through normal deserialization when a JSON value contains a decimal number with hundreds or thousands of fractional digits (e.g.,
"0.000...0001"with 9000+ zeros). The large number of decimal digits produces a huge negativeexponent_offsetinparseNumber(). WhileparseNumber()clamps exponents that exceedexponent_maxin the positive direction, the negative direction is unclamped — the finalexponent += exponent_offsetcan produce values like-9000, which passes through tomake_float()where-ebecomes9000, causing the loop to iterate far beyond the array bounds.Minimal Reproducer
Compile with AddressSanitizer to observe the crash:
ASAN output (abbreviated):
Root Cause Analysis
In
src/ArduinoJson/Numbers/FloatTraits.hpp,make_float():The
positiveBinaryPowersOfTen()/negativeBinaryPowersOfTen()arrays have 9 elements (double) / 6 elements (float). When|e| > 511 (double)or|e| > 63 (float), the loop index exceeds the array bounds.The caller
parseNumber()inparseNumber.hpphas an early-return guard:if (exponent + exponent_offset > traits::exponent_max) { ... }But this only catches the positive direction. The negative direction (large
exponent_offsetfrom many fractional digits) is not clamped, allowing values likee = -9000to reachmake_float().Suggested Fix
Either clamp
ebefore enteringmake_float, or add a defensive bounds check inside the loop:Alternatively, add a symmetric negative clamp in
parseNumber():Environment
-fsanitize=address,undefined)Impact
On hosted platforms (Linux, macOS) with ASAN, this crashes. Without ASAN, it reads adjacent memory and returns garbage float values. On embedded targets (AVR with PROGMEM, ESP32), it reads past the flash/global data section into adjacent memory. The bug is reachable from any untrusted JSON input containing a string or number with hundreds of fractional digits.