Fix fuzzer-found bugs, add libFuzzer harness for v3

Add tests/v3/fuzzer/ with libFuzzer harness covering all four parse
paths (auto-detect, JSON, GLB, float32 mode) with ASan+UBSan.

Fix two bugs found by 10+ hours of fuzzing (~23M iterations):

1. UB: (int64_t)inf in cj_parse_number when extreme exponents like
   22222222e222222 produce infinity. Add cj_dbl_to_i64() that clamps
   inf/NaN/out-of-range values before casting.

2. Null deref in tg3__parse_string when glTF array elements are not
   JSON objects (e.g. "scenes": [[3]]). Add is_object() validation
   in TG3__PARSE_ARRAY_SIMPLE and TG3__PARSE_ARRAY_IDX macros.

Verified clean: 5.8M additional runs with zero crashes after fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Syoyo Fujita
2026-03-21 04:10:25 +09:00
parent 2aeac50277
commit 2c7bf2c932
4 changed files with 200 additions and 3 deletions

View File

@@ -307,6 +307,14 @@ static const char *cj_scan_str(const char *p, const char *end) {
* Breaks strict JSON/IEEE-754-double conformance.
* ====================================================================== */
/* Safe double-to-int64 cast: clamp inf/NaN/out-of-range to 0. */
static int64_t cj_dbl_to_i64(double d) {
if (d != d) return 0; /* NaN */
if (d >= (double)INT64_MAX) return INT64_MAX;
if (d <= (double)INT64_MIN) return INT64_MIN;
return (int64_t)d;
}
/* Exact powers of 10 that are representable as IEEE 754 double.
* 10^0 through 10^22 are all exactly representable. */
static const double cj_exact_pow10[23] = {
@@ -526,7 +534,7 @@ static const char *cj_parse_number(const char *p, const char *end,
if (cj_fast_flt_convert(mantissa, exp10, neg, &f)) {
*is_int = 0;
*dval = (double)f;
*ival = (int64_t)f;
*ival = cj_dbl_to_i64((double)f);
return p;
}
} else {
@@ -534,7 +542,7 @@ static const char *cj_parse_number(const char *p, const char *end,
if (cj_fast_dbl_convert(mantissa, exp10, neg, &d)) {
*is_int = 0;
*dval = d;
*ival = (int64_t)d;
*ival = cj_dbl_to_i64(d);
return p;
}
}
@@ -547,7 +555,7 @@ static const char *cj_parse_number(const char *p, const char *end,
if (float32_mode) d = (double)(float)d;
*is_int = 0;
*dval = d;
*ival = (int64_t)d;
*ival = cj_dbl_to_i64(d);
return eptr;
}