Threat model: parser is intended for server-side processing of attacker-
supplied glTF/GLB. Two adversarial review rounds plus a 1-hour libFuzzer
run (4 workers, ASan+UBSan, ~420M execs total, zero new artifacts) drove
this set of fixes. Concrete PoCs in tests/v3/security/ confirmed each
issue was exploitable on the prior code.
Path traversal (CRITICAL): tg3__load_external_file concatenated base_dir
with the JSON-supplied URI verbatim. A glTF with
"uri":"../../../tmp/secret" successfully loaded the file from outside
base_dir (verified by FNV64 match). New tg3__uri_is_safe rejects empty,
NUL, leading / or \\, Windows drive prefixes, and any '..' segment.
Path-buffer length checks switched to saturating subtraction so 32-bit
size_t cannot wrap.
Sign-coercion in byteStride: int32_t -1 was cast directly to uint32_t,
producing 0xFFFFFFFF and propagating into downstream count*stride math.
Restrict to glTF spec range: 0 (tightly packed) or [4, 252].
Index validation: parsed int32 index fields (accessor.bufferView,
primitive.indices/material/attributes, node.mesh/skin/camera/light,
scene.nodes[], skin.joints[], animation channel/sampler refs, MSFT_lod
ids, KHR_audio emitter/source refs, etc.) were stored unchecked. New
tg3__validate_indices walks every index field and returns
TG3_ERR_INVALID_INDEX on out-of-range. Gated by
tg3_parse_options.validate_indices, defaulting to 1.
Use-after-free on parse failure (PRE-EXISTING, surfaced by ASan during
fix verification): tg3_parse and tg3_parse_glb destroyed model->arena_
on error paths, but error messages on the user-facing tg3_error_stack
were arena-allocated. Any caller reading errors.entries[i].message
after parse failure read freed memory. tg3_model_free is now sole arena
owner; arena lives across error paths so messages stay valid until the
caller frees the model.
Other fixes:
- tg3_parse_glb: hoist tg3__model_init before header parse so callers
can safely tg3_model_free on header failure.
- tg3__parse_primitive morph targets: when arena alloc returns NULL,
pair with target_counts[ti]=0 so validators do not deref.
- Defensive 'if (!tarr) continue' in the morph-target validator loop.
- New Security Considerations block in tiny_gltf_v3.h documents the
threat model, default-on validation, fs-callback contract, and error
message lifetime.
Verification: 13 internal tests in tester_v3_c (incl. 7 new security
regressions covering path traversal absolute and relative, fs-callback
no-call assertion, byteStride wrap, OOB index, opt-in raw mode, ext
fields, and arena-message lifetime), 134/134 Khronos sample models
match v1 ground truth digest, 1-hour ASan+UBSan fuzz on the final code
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce a C-first TinyGLTF v3 runtime in tiny_gltf_v3.c with a pure-C JSON backend, hook the public header to the new implementation, and add CMake/test coverage for parse and write round-trips.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
Replace strtod() with Clinger's fast path in tinygltf_json.h for ~1.5x
faster JSON float parsing. The new parser accumulates all digits into a
uint64 mantissa and uses exact power-of-10 tables for conversion,
avoiding locale-dependent strtod for ~99% of JSON float values.
Add optional float32 parse mode (parse_float32 option) that parses JSON
floats at single precision — fewer significant digits needed, wider fast
path range. Breaks strict double-precision conformance but sufficient
for glTF data which is typically single-precision.
Benchmark additions:
- gen_synthetic: add float_heavy preset (~500MB ASCII float JSON)
- bench_v3: add --float32 flag for float32 parse mode benchmarking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ground-up C-centric rewrite of tinygltf with pure C POD structs,
arena-based memory management, structured error reporting, streaming
callbacks, and no STL dependency in the public API. Uses tinygltf_json.h
as the sole JSON backend.
Includes complete parser (JSON + GLB), writer (JSON + GLB), streaming
writer, C++ RAII wrappers, and C++20 coroutine facade. Verified with
Cube.gltf and Fox.glb parse/write round-trips.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>