Harden and optimize v3 C parser

This commit is contained in:
Syoyo Fujita
2026-05-31 22:20:46 +09:00
parent d31c16e333
commit 0e3043f3e9
6 changed files with 646 additions and 47 deletions

View File

@@ -61,8 +61,10 @@ The v3 C runtime is built for processing **untrusted glTF/GLB input** (server-si
- **URI sanitization** — external buffer/image URIs are rejected before any filesystem call if they are empty, contain NUL bytes, begin with `/` or `\`, look like a Windows drive prefix (`X:`), or contain a `..` segment. Production callers SHOULD still provide a custom `tg3_fs_callbacks.read_file` that confines reads to a known directory (e.g. via `openat` plus a `realpath` prefix check) when the input is attacker-controlled. - **URI sanitization** — external buffer/image URIs are rejected before any filesystem call if they are empty, contain NUL bytes, begin with `/` or `\`, look like a Windows drive prefix (`X:`), or contain a `..` segment. Production callers SHOULD still provide a custom `tg3_fs_callbacks.read_file` that confines reads to a known directory (e.g. via `openat` plus a `realpath` prefix check) when the input is attacker-controlled.
- **Index bounds validation** — every `int32_t` index field populated from JSON (accessor.bufferView, primitive.indices/material/attributes, scene.nodes[], skin.joints[], animation channel/sampler refs, KHR_audio + MSFT_lod refs, …) is checked after the structural parse. Out-of-range indices produce `TG3_ERR_INVALID_INDEX`. Default `tg3_parse_options.validate_indices = 1`; set to `0` only when you need raw round-trip and have your own validator. - **Index bounds validation** — every `int32_t` index field populated from JSON (accessor.bufferView, primitive.indices/material/attributes, scene.nodes[], skin.joints[], animation channel/sampler refs, KHR_audio + MSFT_lod refs, …) is checked after the structural parse. Out-of-range indices produce `TG3_ERR_INVALID_INDEX`. Default `tg3_parse_options.validate_indices = 1`; set to `0` only when you need raw round-trip and have your own validator.
- **Buffer/accessor range validation** — declared buffer lengths, bufferView ranges, accessor element spans, sparse accessor spans, component types, and overflow-prone size math are checked before returning a model.
- **Strict numeric range checks** — JSON numbers feeding integer fields go through finite/round-trip-validated coercion (`tg3__json_number_to_int32` / `_uint64`). `byteStride` is restricted to 0 or [4, 252]. - **Strict numeric range checks** — JSON numbers feeding integer fields go through finite/round-trip-validated coercion (`tg3__json_number_to_int32` / `_uint64`). `byteStride` is restricted to 0 or [4, 252].
- **Memory budget** — the arena is capped at `TINYGLTF3_MAX_MEMORY_BYTES` (1 GB by default; configurable via `tg3_memory_config`). - **Memory budget** — the arena and C JSON parser enforce `TINYGLTF3_MAX_MEMORY_BYTES` by default; `max_single_alloc` and `TINYGLTF3_MAX_STRING_LENGTH` bound individual allocation and string size.
- **Opt-in fast paths** — `skip_extras_values` avoids materializing `extras` and unknown extension value trees, and `borrow_input_buffers` lets GLB buffer spans reference caller-owned input bytes instead of copying the BIN chunk.
- **Image decoding off by default** — the parser does not decode image bytes; use `tg3_parse_options.images_as_is = 1` to skip any decoder entirely when handling untrusted input. - **Image decoding off by default** — the parser does not decode image bytes; use `tg3_parse_options.images_as_is = 1` to skip any decoder entirely when handling untrusted input.
- **Error message lifetime** — error strings on `tg3_error_stack` are arena-allocated and remain valid until `tg3_model_free()`. Read or copy them BEFORE freeing the model. - **Error message lifetime** — error strings on `tg3_error_stack` are arena-allocated and remain valid until `tg3_model_free()`. Read or copy them BEFORE freeing the model.

View File

@@ -139,7 +139,9 @@ struct BenchResult {
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static BenchResult bench_file(const char *filename, int iterations, int warmup, static BenchResult bench_file(const char *filename, int iterations, int warmup,
bool quiet, int float32_mode = 0) { bool quiet, int float32_mode = 0,
int skip_extras_values = 0,
int borrow_input_buffers = 0) {
BenchResult r = {}; BenchResult r = {};
r.filename = filename; r.filename = filename;
r.iterations = iterations; r.iterations = iterations;
@@ -199,6 +201,8 @@ static BenchResult bench_file(const char *filename, int iterations, int warmup,
opts.memory.allocator.free = tracked_free; opts.memory.allocator.free = tracked_free;
opts.memory.allocator.user_data = &tracker; opts.memory.allocator.user_data = &tracker;
opts.parse_float32 = float32_mode; opts.parse_float32 = float32_mode;
opts.skip_extras_values = skip_extras_values;
opts.borrow_input_buffers = borrow_input_buffers;
tg3_model model; tg3_model model;
tg3_error_stack errors; tg3_error_stack errors;
@@ -340,7 +344,11 @@ static void usage() {
" --csv Output in CSV format\n" " --csv Output in CSV format\n"
" --quiet Suppress per-iteration error messages\n" " --quiet Suppress per-iteration error messages\n"
" --batch Benchmark multiple files\n" " --batch Benchmark multiple files\n"
" --float32 Parse JSON floats as float32 (faster, less precise)\n"); " --float32 Parse JSON floats as float32 (faster, less precise)\n"
" --skip-extras-values\n"
" Skip materializing extras/unknown extension values\n"
" --borrow-input-buffers\n"
" Let GLB buffers reference caller-owned input bytes\n");
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
@@ -351,6 +359,8 @@ int main(int argc, char **argv) {
bool csv = false; bool csv = false;
bool quiet = false; bool quiet = false;
int float32_mode = 0; int float32_mode = 0;
int skip_extras_values = 0;
int borrow_input_buffers = 0;
std::vector<std::string> files; std::vector<std::string> files;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
@@ -364,6 +374,10 @@ int main(int argc, char **argv) {
quiet = true; quiet = true;
} else if (strcmp(argv[i], "--float32") == 0) { } else if (strcmp(argv[i], "--float32") == 0) {
float32_mode = 1; float32_mode = 1;
} else if (strcmp(argv[i], "--skip-extras-values") == 0) {
skip_extras_values = 1;
} else if (strcmp(argv[i], "--borrow-input-buffers") == 0) {
borrow_input_buffers = 1;
} else if (strcmp(argv[i], "--batch") == 0) { } else if (strcmp(argv[i], "--batch") == 0) {
/* batch mode: just collect files */ /* batch mode: just collect files */
} else if (argv[i][0] != '-') { } else if (argv[i][0] != '-') {
@@ -379,10 +393,14 @@ int main(int argc, char **argv) {
if (!csv && !quiet) { if (!csv && !quiet) {
printf("Benchmarking: %s (%d iterations, %d warmup%s)\n", printf("Benchmarking: %s (%d iterations, %d warmup%s)\n",
file.c_str(), iterations, warmup, file.c_str(), iterations, warmup,
float32_mode ? ", float32" : ""); float32_mode ? ", float32" :
skip_extras_values ? ", skip extras" :
borrow_input_buffers ? ", borrow buffers" : "");
} }
BenchResult r = bench_file(file.c_str(), iterations, warmup, quiet, float32_mode); BenchResult r = bench_file(file.c_str(), iterations, warmup, quiet,
float32_mode, skip_extras_values,
borrow_input_buffers);
if (csv) { if (csv) {
print_csv_row(r); print_csv_row(r);

View File

@@ -649,6 +649,196 @@ fail:
return 0; return 0;
} }
static int check_buffer_view_range_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"uri\":\"data:application/octet-stream;base64,AAAA\",\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteOffset\":2,\"byteLength\":4}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_INVALID_ACCESSOR) {
fprintf(stderr, "bufferView OOB expected TG3_ERR_INVALID_ACCESSOR, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_accessor_range_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"uri\":\"data:application/octet-stream;base64,AAAA\",\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4}],"
"\"accessors\":[{\"bufferView\":0,\"componentType\":5126,\"count\":2,\"type\":\"SCALAR\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_INVALID_ACCESSOR) {
fprintf(stderr, "accessor OOB expected TG3_ERR_INVALID_ACCESSOR, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_sparse_accessor_range_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"uri\":\"data:application/octet-stream;base64,AAAAAAAA\",\"byteLength\":6}],"
"\"bufferViews\":[{\"buffer\":0,\"byteOffset\":0,\"byteLength\":2},"
"{\"buffer\":0,\"byteOffset\":2,\"byteLength\":4}],"
"\"accessors\":[{\"componentType\":5126,\"count\":2,\"type\":\"SCALAR\","
"\"sparse\":{\"count\":2,\"indices\":{\"bufferView\":0,\"componentType\":5123},"
"\"values\":{\"bufferView\":1}}}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_INVALID_ACCESSOR) {
fprintf(stderr, "sparse accessor OOB expected TG3_ERR_INVALID_ACCESSOR, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_skip_extras_values_opt_in(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"extras\":{\"large\":[1,2,3]},"
"\"nodes\":[{\"extensions\":{\"VENDOR_test\":{\"x\":1}}}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_OK || !model.ext.extras || model.ext.extras->type != TG3_VALUE_OBJECT) {
fprintf(stderr, "default extras materialization failed\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.skip_extras_values = 1;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_OK || model.ext.extras != NULL ||
model.nodes_count != 1 || model.nodes[0].ext.extensions_count != 1 ||
model.nodes[0].ext.extensions[0].value.type != TG3_VALUE_NULL) {
fprintf(stderr, "skip_extras_values did not skip value trees as expected\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_json_limits_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"abcdef\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.memory.max_single_alloc = 4;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_JSON_PARSE) {
fprintf(stderr, "small max_single_alloc expected JSON parse failure, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_borrow_input_buffers(void) {
static const char json[] =
"{\"asset\":{\"version\":\"2.0\"},\"buffers\":[{\"byteLength\":4}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
uint32_t json_len = (uint32_t)(sizeof(json) - 1);
uint32_t json_padded = (json_len + 3u) & ~3u;
uint32_t bin_len = 4;
uint32_t total = 12u + 8u + json_padded + 8u + bin_len;
uint32_t version = 2;
uint32_t json_type = 0x4E4F534Au;
uint32_t bin_type = 0x004E4942u;
uint8_t *glb = (uint8_t *)malloc(total);
uint32_t bin_off = 12u + 8u + json_padded + 8u;
int ok = 0;
if (!glb) return 0;
memset(glb, ' ', total);
memcpy(glb, "glTF", 4);
memcpy(glb + 4, &version, 4);
memcpy(glb + 8, &total, 4);
memcpy(glb + 12, &json_padded, 4);
memcpy(glb + 16, &json_type, 4);
memcpy(glb + 20, json, json_len);
memcpy(glb + 20 + json_padded, &bin_len, 4);
memcpy(glb + 24 + json_padded, &bin_type, 4);
glb[bin_off + 0] = 1;
glb[bin_off + 1] = 2;
glb[bin_off + 2] = 3;
glb[bin_off + 3] = 4;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.borrow_input_buffers = 1;
err = tg3_parse_glb(&model, &errors, glb, (uint64_t)total, "", 0, &opts);
if (err == TG3_OK && model.buffers_count == 1 &&
model.buffers[0].data.data == glb + bin_off &&
model.buffers[0].data.count == 4) {
ok = 1;
} else {
fprintf(stderr, "borrow_input_buffers failed: err=%d buffers=%u\n",
(int)err, model.buffers_count);
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
free(glb);
return ok;
}
static int parse_file_arg(const char *path) { static int parse_file_arg(const char *path) {
FILE *fp = fopen(path, "rb"); FILE *fp = fopen(path, "rb");
uint8_t *buf; uint8_t *buf;
@@ -768,5 +958,23 @@ int main(int argc, char **argv) {
if (!check_error_messages_survive_parse_failure()) { if (!check_error_messages_survive_parse_failure()) {
return 1; return 1;
} }
if (!check_buffer_view_range_rejected()) {
return 1;
}
if (!check_accessor_range_rejected()) {
return 1;
}
if (!check_sparse_accessor_range_rejected()) {
return 1;
}
if (!check_skip_extras_values_opt_in()) {
return 1;
}
if (!check_json_limits_rejected()) {
return 1;
}
if (!check_borrow_input_buffers()) {
return 1;
}
return 0; return 0;
} }

View File

@@ -504,6 +504,18 @@ static double tg3__json_number_to_double(const tg3json_value *v) {
static int tg3__json_is_object(const tg3json_value *v) { return v && v->type == TG3JSON_OBJECT; } static int tg3__json_is_object(const tg3json_value *v) { return v && v->type == TG3JSON_OBJECT; }
static int tg3__json_is_array(const tg3json_value *v) { return v && v->type == TG3JSON_ARRAY; } static int tg3__json_is_array(const tg3json_value *v) { return v && v->type == TG3JSON_ARRAY; }
static int tg3__u64_add_overflow(uint64_t a, uint64_t b, uint64_t *out) {
if (a > UINT64_MAX - b) return 1;
*out = a + b;
return 0;
}
static int tg3__u64_mul_overflow(uint64_t a, uint64_t b, uint64_t *out) {
if (a != 0 && b > UINT64_MAX / a) return 1;
*out = a * b;
return 0;
}
static int tg3__json_has(const tg3json_value *o, const char *key) { static int tg3__json_has(const tg3json_value *o, const char *key) {
return tg3json_object_get(o, key) ? 1 : 0; return tg3json_object_get(o, key) ? 1 : 0;
} }
@@ -830,10 +842,12 @@ static void tg3__parse_extras_and_extensions(tg3__parse_ctx *ctx, const tg3json_
memset(ee, 0, sizeof(*ee)); memset(ee, 0, sizeof(*ee));
extras_it = tg3__json_get(o, "extras"); extras_it = tg3__json_get(o, "extras");
if (extras_it) { if (extras_it) {
tg3_value *ev = (tg3_value *)tg3__arena_alloc(ctx->arena, sizeof(tg3_value)); if (!ctx->opts.skip_extras_values) {
if (ev) { tg3_value *ev = (tg3_value *)tg3__arena_alloc(ctx->arena, sizeof(tg3_value));
*ev = tg3__json_to_value(ctx, extras_it); if (ev) {
ee->extras = ev; *ev = tg3__json_to_value(ctx, extras_it);
ee->extras = ev;
}
} }
if (ctx->opts.store_original_json) { if (ctx->opts.store_original_json) {
size_t raw_len = 0; size_t raw_len = 0;
@@ -853,8 +867,13 @@ static void tg3__parse_extras_and_extensions(tg3__parse_ctx *ctx, const tg3json_
if (exts) { if (exts) {
for (i = 0; i < count; ++i) { for (i = 0; i < count; ++i) {
const tg3json_object_entry *entry = tg3json_object_at(ext_it, i); const tg3json_object_entry *entry = tg3json_object_at(ext_it, i);
memset(&exts[i], 0, sizeof(exts[i]));
exts[i].name = tg3__arena_str(ctx->arena, entry->key, (uint32_t)entry->key_len); exts[i].name = tg3__arena_str(ctx->arena, entry->key, (uint32_t)entry->key_len);
exts[i].value = tg3__json_to_value(ctx, entry->value); if (!ctx->opts.skip_extras_values) {
exts[i].value = tg3__json_to_value(ctx, entry->value);
} else {
exts[i].value.type = TG3_VALUE_NULL;
}
} }
ee->extensions = exts; ee->extensions = exts;
ee->extensions_count = (uint32_t)count; ee->extensions_count = (uint32_t)count;
@@ -1096,6 +1115,7 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o,
tg3__parse_string(ctx, o, "name", &buf->name, 0, "/buffer"); tg3__parse_string(ctx, o, "name", &buf->name, 0, "/buffer");
tg3__parse_string(ctx, o, "uri", &buf->uri, 0, "/buffer"); tg3__parse_string(ctx, o, "uri", &buf->uri, 0, "/buffer");
tg3__parse_uint64(ctx, o, "byteLength", &byte_length, 1, "/buffer"); tg3__parse_uint64(ctx, o, "byteLength", &byte_length, 1, "/buffer");
buf->byte_length = byte_length;
if (ctx->is_binary && buf_idx == 0 && buf->uri.len == 0) { if (ctx->is_binary && buf_idx == 0 && buf->uri.len == 0) {
uint8_t *data; uint8_t *data;
if (!ctx->bin_data || ctx->bin_size < byte_length) { if (!ctx->bin_data || ctx->bin_size < byte_length) {
@@ -1103,13 +1123,24 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o,
"GLB BIN chunk missing or smaller than buffer.byteLength", NULL, -1); "GLB BIN chunk missing or smaller than buffer.byteLength", NULL, -1);
return 0; return 0;
} }
if (ctx->opts.borrow_input_buffers) {
buf->data.data = ctx->bin_data;
buf->data.count = byte_length;
tg3__parse_extras_and_extensions(ctx, o, &buf->ext);
return 1;
}
if (byte_length > (uint64_t)((size_t)-1)) {
tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
"buffer.byteLength exceeds addressable size", NULL, -1);
return 0;
}
data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)byte_length); data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)byte_length);
if (!data) { if (!data && byte_length > 0) {
tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY, tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
"OOM for buffer data", NULL, -1); "OOM for buffer data", NULL, -1);
return 0; return 0;
} }
memcpy(data, ctx->bin_data, (size_t)byte_length); if (byte_length > 0) memcpy(data, ctx->bin_data, (size_t)byte_length);
buf->data.data = data; buf->data.data = data;
buf->data.count = byte_length; buf->data.count = byte_length;
} else if (buf->uri.len > 0) { } else if (buf->uri.len > 0) {
@@ -1130,11 +1161,22 @@ static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3json_value *o,
uint8_t *file_data = NULL; uint8_t *file_data = NULL;
uint64_t file_size = 0; uint64_t file_size = 0;
if (tg3__load_external_file(ctx, &file_data, &file_size, buf->uri.data, buf->uri.len)) { if (tg3__load_external_file(ctx, &file_data, &file_size, buf->uri.data, buf->uri.len)) {
uint8_t *data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)file_size); uint8_t *data = NULL;
if (file_size > (uint64_t)((size_t)-1)) {
tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
"external buffer exceeds addressable size", NULL, -1);
if (ctx->opts.fs.free_file) ctx->opts.fs.free_file(file_data, file_size, ctx->opts.fs.user_data);
tg3__parse_extras_and_extensions(ctx, o, &buf->ext);
return 0;
}
data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)file_size);
if (data) { if (data) {
memcpy(data, file_data, (size_t)file_size); memcpy(data, file_data, (size_t)file_size);
buf->data.data = data; buf->data.data = data;
buf->data.count = file_size; buf->data.count = file_size;
} else if (file_size > 0) {
tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
"OOM for external buffer data", NULL, -1);
} }
if (ctx->opts.fs.free_file) ctx->opts.fs.free_file(file_data, file_size, ctx->opts.fs.user_data); if (ctx->opts.fs.free_file) ctx->opts.fs.free_file(file_data, file_size, ctx->opts.fs.user_data);
} }
@@ -1808,6 +1850,176 @@ static int tg3__validate_indices(tg3__parse_ctx *ctx, const tg3_model *m) {
return errs == 0; return errs == 0;
} }
static int tg3__valid_accessor_component_type(int32_t component_type) {
switch (component_type) {
case TG3_COMPONENT_TYPE_BYTE:
case TG3_COMPONENT_TYPE_UNSIGNED_BYTE:
case TG3_COMPONENT_TYPE_SHORT:
case TG3_COMPONENT_TYPE_UNSIGNED_SHORT:
case TG3_COMPONENT_TYPE_UNSIGNED_INT:
case TG3_COMPONENT_TYPE_FLOAT:
return 1;
default:
return 0;
}
}
static int tg3__valid_sparse_index_component_type(int32_t component_type) {
return component_type == TG3_COMPONENT_TYPE_UNSIGNED_BYTE ||
component_type == TG3_COMPONENT_TYPE_UNSIGNED_SHORT ||
component_type == TG3_COMPONENT_TYPE_UNSIGNED_INT;
}
static int tg3__validate_range_in_buffer_view(tg3__parse_ctx *ctx,
const tg3_buffer_view *bv,
uint64_t byte_offset,
uint64_t byte_length,
const char *what) {
uint64_t end;
int overflow = tg3__u64_add_overflow(byte_offset, byte_length, &end);
if (overflow || end > bv->byte_length) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_ACCESSOR, "/accessors",
"%s byte range [%llu,%llu) exceeds bufferView length %llu",
what,
(unsigned long long)byte_offset,
(unsigned long long)(overflow ? UINT64_MAX : end),
(unsigned long long)bv->byte_length);
return 0;
}
return 1;
}
static int tg3__validate_resources(tg3__parse_ctx *ctx, const tg3_model *m) {
uint32_t i;
int ok = 1;
for (i = 0; i < m->buffers_count; ++i) {
const tg3_buffer *b = &m->buffers[i];
if (b->data.count > 0 && b->byte_length > b->data.count) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_BUFFER_SIZE_MISMATCH, "/buffers",
"buffers[%u].byteLength %llu exceeds loaded data size %llu",
i, (unsigned long long)b->byte_length,
(unsigned long long)b->data.count);
ok = 0;
}
}
for (i = 0; i < m->buffer_views_count; ++i) {
const tg3_buffer_view *bv = &m->buffer_views[i];
const tg3_buffer *b;
uint64_t buffer_size;
uint64_t end;
if (bv->buffer < 0 || (uint32_t)bv->buffer >= m->buffers_count) {
continue;
}
b = &m->buffers[bv->buffer];
buffer_size = b->byte_length ? b->byte_length : b->data.count;
if (tg3__u64_add_overflow(bv->byte_offset, bv->byte_length, &end) ||
end > buffer_size) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_BUFFER_VIEW, "/bufferViews",
"bufferViews[%u] byte range exceeds buffers[%d].byteLength",
i, bv->buffer);
ok = 0;
}
}
for (i = 0; i < m->accessors_count; ++i) {
const tg3_accessor *a = &m->accessors[i];
int32_t comp_size;
int32_t num_comp;
uint64_t elem_size;
if (!tg3__valid_accessor_component_type(a->component_type)) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_ACCESSOR, "/accessors",
"accessors[%u].componentType %d is invalid",
i, a->component_type);
ok = 0;
continue;
}
comp_size = tg3_component_size(a->component_type);
num_comp = tg3_num_components(a->type);
if (comp_size <= 0 || num_comp <= 0) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_ACCESSOR, "/accessors",
"accessors[%u].type %d is invalid", i, a->type);
ok = 0;
continue;
}
if (tg3__u64_mul_overflow((uint64_t)comp_size, (uint64_t)num_comp, &elem_size)) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_ACCESSOR, "/accessors",
"accessors[%u] element size overflows", i);
ok = 0;
continue;
}
if (a->buffer_view >= 0 && (uint32_t)a->buffer_view < m->buffer_views_count) {
const tg3_buffer_view *bv = &m->buffer_views[a->buffer_view];
uint64_t stride = bv->byte_stride ? (uint64_t)bv->byte_stride : elem_size;
uint64_t last_offset = 0;
uint64_t span = 0;
if (stride < elem_size ||
(a->count > 0 &&
(tg3__u64_mul_overflow(a->count - 1u, stride, &last_offset) ||
tg3__u64_add_overflow(last_offset, elem_size, &span)))) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_ACCESSOR, "/accessors",
"accessors[%u] byte stride/count overflows", i);
ok = 0;
} else if (!tg3__validate_range_in_buffer_view(ctx, bv, a->byte_offset,
a->count > 0 ? span : 0,
"accessor")) {
ok = 0;
}
}
if (a->sparse.is_sparse) {
uint64_t sparse_count;
if (a->sparse.count < 0 || (uint64_t)a->sparse.count > a->count) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_ACCESSOR, "/accessors",
"accessors[%u].sparse.count %d is invalid",
i, a->sparse.count);
ok = 0;
continue;
}
sparse_count = (uint64_t)a->sparse.count;
if (!tg3__valid_sparse_index_component_type(a->sparse.indices.component_type)) {
tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
TG3_ERR_INVALID_ACCESSOR, "/accessors",
"accessors[%u].sparse.indices.componentType %d is invalid",
i, a->sparse.indices.component_type);
ok = 0;
}
if (a->sparse.indices.buffer_view >= 0 &&
(uint32_t)a->sparse.indices.buffer_view < m->buffer_views_count) {
const tg3_buffer_view *bv = &m->buffer_views[a->sparse.indices.buffer_view];
uint64_t bytes = 0;
int32_t idx_size_i = tg3_component_size(a->sparse.indices.component_type);
if (idx_size_i <= 0 ||
tg3__u64_mul_overflow(sparse_count, (uint64_t)idx_size_i, &bytes) ||
!tg3__validate_range_in_buffer_view(ctx, bv, a->sparse.indices.byte_offset,
bytes, "sparse indices")) {
ok = 0;
}
}
if (a->sparse.values.buffer_view >= 0 &&
(uint32_t)a->sparse.values.buffer_view < m->buffer_views_count) {
const tg3_buffer_view *bv = &m->buffer_views[a->sparse.values.buffer_view];
uint64_t bytes = 0;
if (tg3__u64_mul_overflow(sparse_count, elem_size, &bytes) ||
!tg3__validate_range_in_buffer_view(ctx, bv, a->sparse.values.byte_offset,
bytes, "sparse values")) {
ok = 0;
}
}
}
}
return ok;
}
static tg3_error_code tg3__parse_from_json(tg3__parse_ctx *ctx, const tg3json_value *json_doc, tg3_model *model) { static tg3_error_code tg3__parse_from_json(tg3__parse_ctx *ctx, const tg3json_value *json_doc, tg3_model *model) {
const tg3json_value *asset_it = tg3json_object_get(json_doc, "asset"); const tg3json_value *asset_it = tg3json_object_get(json_doc, "asset");
const tg3json_value *ext_it; const tg3json_value *ext_it;
@@ -1873,9 +2085,22 @@ static tg3_error_code tg3__parse_from_json(tg3__parse_ctx *ctx, const tg3json_va
return TG3_ERR_INVALID_INDEX; return TG3_ERR_INVALID_INDEX;
} }
} }
if (!tg3__validate_resources(ctx, model)) {
return TG3_ERR_INVALID_ACCESSOR;
}
return (ctx->errors && ctx->errors->has_error) ? TG3_ERR_JSON_PARSE : TG3_OK; return (ctx->errors && ctx->errors->has_error) ? TG3_ERR_JSON_PARSE : TG3_OK;
} }
static void tg3__json_parse_options_from_tg3(const tg3_parse_options *options,
tg3json_parse_options *json_options) {
memset(json_options, 0, sizeof(*json_options));
json_options->depth_limit = TINYGLTF3_MAX_NESTING_DEPTH;
json_options->memory_budget = options ? (size_t)options->memory.memory_budget : 0;
json_options->max_single_alloc = options ? (size_t)options->memory.max_single_alloc : 0;
json_options->max_string_length = TINYGLTF3_MAX_STRING_LENGTH;
json_options->parse_float32 = options ? options->parse_float32 : 0;
}
static tg3_error_code tg3__parse_glb_header(const uint8_t *data, uint64_t size, static tg3_error_code tg3__parse_glb_header(const uint8_t *data, uint64_t size,
const uint8_t **json_out, uint64_t *json_size_out, const uint8_t **json_out, uint64_t *json_size_out,
const uint8_t **bin_out, uint64_t *bin_size_out, const uint8_t **bin_out, uint64_t *bin_size_out,
@@ -1901,17 +2126,17 @@ static tg3_error_code tg3__parse_glb_header(const uint8_t *data, uint64_t size,
return TG3_ERR_GLB_INVALID_VERSION; return TG3_ERR_GLB_INVALID_VERSION;
} }
memcpy(&total_length, data + 8, 4); memcpy(&total_length, data + 8, 4);
if ((uint64_t)total_length > size) { if ((uint64_t)total_length != size) {
tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_SIZE_MISMATCH, tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_SIZE_MISMATCH,
"GLB total length exceeds data size", NULL, -1); "GLB total length does not match data size", NULL, -1);
return TG3_ERR_GLB_SIZE_MISMATCH; return TG3_ERR_GLB_SIZE_MISMATCH;
} }
while (offset + 8 <= size) { while (offset + 8 <= (uint64_t)total_length) {
uint32_t chunk_length; uint32_t chunk_length;
uint32_t chunk_type; uint32_t chunk_type;
memcpy(&chunk_length, data + offset, 4); offset += 4; memcpy(&chunk_length, data + offset, 4); offset += 4;
memcpy(&chunk_type, data + offset, 4); offset += 4; memcpy(&chunk_type, data + offset, 4); offset += 4;
if (offset + chunk_length > size) { if (offset + chunk_length > (uint64_t)total_length) {
tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_CHUNK_ERROR, tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_CHUNK_ERROR,
"GLB chunk exceeds data size", NULL, -1); "GLB chunk exceeds data size", NULL, -1);
return TG3_ERR_GLB_CHUNK_ERROR; return TG3_ERR_GLB_CHUNK_ERROR;
@@ -1942,19 +2167,23 @@ TINYGLTF3_API tg3_error_code tg3_parse(tg3_model *model, tg3_error_stack *errors
tg3_arena *arena; tg3_arena *arena;
tg3__parse_ctx ctx; tg3__parse_ctx ctx;
tg3json_value json_doc; tg3json_value json_doc;
tg3json_parse_options json_options;
const char *error_pos = NULL; const char *error_pos = NULL;
tg3_error_code ret; tg3_error_code ret;
int parsed_ok; int parsed_ok;
if (!options) { tg3_parse_options_init(&default_opts); options = &default_opts; } if (!model) return TG3_ERR_JSON_PARSE;
tg3__model_init(model); tg3__model_init(model);
if (!json_data) return TG3_ERR_JSON_PARSE;
if (!options) { tg3_parse_options_init(&default_opts); options = &default_opts; }
arena = tg3__arena_create(&options->memory); arena = tg3__arena_create(&options->memory);
if (!arena) { if (!arena) {
tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY, "Failed to create arena", NULL, -1); tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY, "Failed to create arena", NULL, -1);
return TG3_ERR_OUT_OF_MEMORY; return TG3_ERR_OUT_OF_MEMORY;
} }
model->arena_ = arena; model->arena_ = arena;
parsed_ok = tg3json_parse_n((const char *)json_data, (size_t)json_size, tg3__json_parse_options_from_tg3(options, &json_options);
TINYGLTF3_MAX_NESTING_DEPTH, &json_doc, &error_pos); parsed_ok = tg3json_parse_n_opts((const char *)json_data, (size_t)json_size,
&json_options, &json_doc, &error_pos);
if (!parsed_ok || json_doc.type != TG3JSON_OBJECT) { if (!parsed_ok || json_doc.type != TG3JSON_OBJECT) {
tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE, "Failed to parse JSON", NULL, tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE, "Failed to parse JSON", NULL,
error_pos ? (int64_t)(error_pos - (const char *)json_data) : -1); error_pos ? (int64_t)(error_pos - (const char *)json_data) : -1);
@@ -1992,13 +2221,16 @@ TINYGLTF3_API tg3_error_code tg3_parse_glb(tg3_model *model, tg3_error_stack *er
tg3_arena *arena; tg3_arena *arena;
tg3__parse_ctx ctx; tg3__parse_ctx ctx;
tg3json_value json_doc; tg3json_value json_doc;
tg3json_parse_options json_options;
const char *error_pos = NULL; const char *error_pos = NULL;
tg3_error_code err; tg3_error_code err;
int parsed_ok; int parsed_ok;
/* Initialize model before any failure-return so callers can safely call /* Initialize model before any failure-return so callers can safely call
* tg3_model_free() on the error path; the GLB header parse must not run * tg3_model_free() on the error path; the GLB header parse must not run
* against a model whose arena_ field is uninitialized garbage. */ * against a model whose arena_ field is uninitialized garbage. */
if (!model) return TG3_ERR_GLB_INVALID_HEADER;
tg3__model_init(model); tg3__model_init(model);
if (!glb_data) return TG3_ERR_GLB_INVALID_HEADER;
err = tg3__parse_glb_header(glb_data, glb_size, &json_chunk, &json_chunk_size, &bin_chunk, &bin_chunk_size, errors); err = tg3__parse_glb_header(glb_data, glb_size, &json_chunk, &json_chunk_size, &bin_chunk, &bin_chunk_size, errors);
if (err != TG3_OK) return err; if (err != TG3_OK) return err;
if (!options) { tg3_parse_options_init(&default_opts); options = &default_opts; } if (!options) { tg3_parse_options_init(&default_opts); options = &default_opts; }
@@ -2008,8 +2240,9 @@ TINYGLTF3_API tg3_error_code tg3_parse_glb(tg3_model *model, tg3_error_stack *er
return TG3_ERR_OUT_OF_MEMORY; return TG3_ERR_OUT_OF_MEMORY;
} }
model->arena_ = arena; model->arena_ = arena;
parsed_ok = tg3json_parse_n((const char *)json_chunk, (size_t)json_chunk_size, tg3__json_parse_options_from_tg3(options, &json_options);
TINYGLTF3_MAX_NESTING_DEPTH, &json_doc, &error_pos); parsed_ok = tg3json_parse_n_opts((const char *)json_chunk, (size_t)json_chunk_size,
&json_options, &json_doc, &error_pos);
if (!parsed_ok || json_doc.type != TG3JSON_OBJECT) { if (!parsed_ok || json_doc.type != TG3JSON_OBJECT) {
tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE, "Failed to parse GLB JSON chunk", NULL, tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE, "Failed to parse GLB JSON chunk", NULL,
error_pos ? (int64_t)(error_pos - (const char *)json_chunk) : -1); error_pos ? (int64_t)(error_pos - (const char *)json_chunk) : -1);
@@ -2039,6 +2272,11 @@ TINYGLTF3_API tg3_error_code tg3_parse_auto(tg3_model *model, tg3_error_stack *e
const uint8_t *data, uint64_t size, const uint8_t *data, uint64_t size,
const char *base_dir, uint32_t base_dir_len, const char *base_dir, uint32_t base_dir_len,
const tg3_parse_options *options) { const tg3_parse_options *options) {
if (!model) return TG3_ERR_JSON_PARSE;
if (!data && size > 0) {
tg3__model_init(model);
return TG3_ERR_JSON_PARSE;
}
if (size >= 4 && data[0] == 'g' && data[1] == 'l' && data[2] == 'T' && data[3] == 'F') { if (size >= 4 && data[0] == 'g' && data[1] == 'l' && data[2] == 'T' && data[3] == 'F') {
return tg3_parse_glb(model, errors, data, size, base_dir, base_dir_len, options); return tg3_parse_glb(model, errors, data, size, base_dir, base_dir_len, options);
} }
@@ -2055,9 +2293,11 @@ TINYGLTF3_API tg3_error_code tg3_parse_file(tg3_model *model, tg3_error_stack *e
uint32_t base_dir_len = 0; uint32_t base_dir_len = 0;
tg3_error_code result; tg3_error_code result;
uint32_t i; uint32_t i;
if (!model) return TG3_ERR_FILE_NOT_FOUND;
tg3__model_init(model);
if (!filename) return TG3_ERR_FILE_NOT_FOUND;
if (options) opts = *options; if (options) opts = *options;
else tg3_parse_options_init(&opts); else tg3_parse_options_init(&opts);
tg3__model_init(model);
#ifdef TINYGLTF3_ENABLE_FS #ifdef TINYGLTF3_ENABLE_FS
tg3__set_default_fs(&opts.fs); tg3__set_default_fs(&opts.fs);
#endif #endif
@@ -2464,7 +2704,8 @@ static int tg3__serialize_buffer(const tg3_buffer *b, int wd, int embed, tg3json
(void)wd; (void)wd;
tg3json_value_init_object(out); tg3json_value_init_object(out);
if (!tg3__serialize_str(out, "name", b->name) || if (!tg3__serialize_str(out, "name", b->name) ||
!tg3__json_set_int(out, "byteLength", (int64_t)b->data.count)) { !tg3__json_set_int(out, "byteLength",
(int64_t)(b->byte_length ? b->byte_length : b->data.count))) {
tg3json_value_free(out); tg3json_value_free(out);
return 0; return 0;
} }

View File

@@ -418,6 +418,7 @@ typedef struct tg3_asset {
/* --- Buffer --- */ /* --- Buffer --- */
typedef struct tg3_buffer { typedef struct tg3_buffer {
tg3_str name; tg3_str name;
uint64_t byte_length; /* Declared buffer.byteLength */
tg3_span_u8 data; tg3_span_u8 data;
tg3_str uri; tg3_str uri;
tg3_extras_ext ext; tg3_extras_ext ext;
@@ -941,6 +942,10 @@ typedef struct tg3_parse_options {
int32_t images_as_is; /* 1 = don't decode images */ int32_t images_as_is; /* 1 = don't decode images */
int32_t preserve_image_channels; /* 1 = keep original channels */ int32_t preserve_image_channels; /* 1 = keep original channels */
int32_t store_original_json; /* 1 = store raw JSON strings */ int32_t store_original_json; /* 1 = store raw JSON strings */
int32_t skip_extras_values; /* 1 = skip materializing extras and
* unknown extension value trees */
int32_t borrow_input_buffers; /* 1 = GLB BIN buffer spans may point
* into caller-owned input data */
int32_t parse_float32; /* 1 = parse JSON floats as float32 for speed int32_t parse_float32; /* 1 = parse JSON floats as float32 for speed
* (breaks strict double-precision conformance * (breaks strict double-precision conformance
* but sufficient for glTF data which is * but sufficient for glTF data which is

View File

@@ -58,6 +58,17 @@ int tg3json_parse_n(const char *data, size_t len, size_t depth_limit,
tg3json_value *out_value, const char **out_error_pos); tg3json_value *out_value, const char **out_error_pos);
int tg3json_parse(const char *begin, const char *end, size_t depth_limit, int tg3json_parse(const char *begin, const char *end, size_t depth_limit,
tg3json_value *out_value, const char **out_error_pos); tg3json_value *out_value, const char **out_error_pos);
typedef struct tg3json_parse_options {
size_t depth_limit; /* 0 = default */
size_t memory_budget; /* 0 = unlimited */
size_t max_single_alloc; /* 0 = unlimited */
size_t max_string_length; /* 0 = unlimited */
int parse_float32; /* 1 = round JSON reals to float */
} tg3json_parse_options;
int tg3json_parse_n_opts(const char *data, size_t len,
const tg3json_parse_options *options,
tg3json_value *out_value,
const char **out_error_pos);
void tg3json_value_free(tg3json_value *value); void tg3json_value_free(tg3json_value *value);
void tg3json_value_init_null(tg3json_value *value); void tg3json_value_init_null(tg3json_value *value);
void tg3json_value_init_bool(tg3json_value *value, int boolean_value); void tg3json_value_init_bool(tg3json_value *value, int boolean_value);
@@ -109,9 +120,15 @@ typedef struct tg3json__parser {
const char *end; const char *end;
const char *error; const char *error;
size_t depth_limit; size_t depth_limit;
size_t memory_budget;
size_t max_single_alloc;
size_t max_string_length;
size_t allocated;
int parse_float32;
} tg3json__parser; } tg3json__parser;
typedef struct tg3json__buffer { typedef struct tg3json__buffer {
tg3json__parser *parser;
char *data; char *data;
size_t len; size_t len;
size_t cap; size_t cap;
@@ -131,6 +148,20 @@ static char *tg3json__strndup_local(const char *src, size_t len) {
return dst; return dst;
} }
static void *tg3json__parser_alloc(tg3json__parser *parser, size_t size) {
void *ptr;
if (!parser) return malloc(size);
if (parser->max_single_alloc && size > parser->max_single_alloc) return NULL;
if (parser->memory_budget &&
(size > parser->memory_budget || parser->allocated > parser->memory_budget - size)) {
return NULL;
}
ptr = malloc(size);
if (!ptr) return NULL;
parser->allocated += size;
return ptr;
}
static int tg3json__reserve_bytes(void **ptr, size_t elem_size, static int tg3json__reserve_bytes(void **ptr, size_t elem_size,
size_t needed, size_t *capacity) { size_t needed, size_t *capacity) {
void *new_ptr; void *new_ptr;
@@ -154,6 +185,44 @@ static int tg3json__reserve_bytes(void **ptr, size_t elem_size,
return 1; return 1;
} }
static int tg3json__reserve_bytes_parser(tg3json__parser *parser, void **ptr,
size_t elem_size, size_t needed,
size_t *capacity) {
void *new_ptr;
size_t new_cap;
size_t old_bytes;
size_t new_bytes;
if (needed <= *capacity) return 1;
new_cap = (*capacity > 0) ? *capacity : 8;
while (new_cap < needed) {
if (new_cap > ((size_t)-1) / 2) {
new_cap = needed;
break;
}
new_cap *= 2;
}
if (elem_size != 0 && new_cap > ((size_t)-1) / elem_size) return 0;
old_bytes = elem_size * (*capacity);
new_bytes = elem_size * new_cap;
if (parser) {
size_t delta = (new_bytes > old_bytes) ? (new_bytes - old_bytes) : 0;
if (parser->max_single_alloc && new_bytes > parser->max_single_alloc) return 0;
if (parser->memory_budget && delta > 0 &&
(delta > parser->memory_budget ||
parser->allocated > parser->memory_budget - delta)) {
return 0;
}
}
new_ptr = realloc(*ptr, new_bytes);
if (!new_ptr) return 0;
if (parser && new_bytes > old_bytes) parser->allocated += new_bytes - old_bytes;
*ptr = new_ptr;
*capacity = new_cap;
return 1;
}
static const char *tg3json__skip_ws(const char *p, const char *end) { static const char *tg3json__skip_ws(const char *p, const char *end) {
while (p < end) { while (p < end) {
unsigned char c = (unsigned char)*p; unsigned char c = (unsigned char)*p;
@@ -169,7 +238,8 @@ static void tg3json__set_error(tg3json__parser *parser, const char *pos) {
static int tg3json__buf_append(tg3json__buffer *buf, const char *src, size_t len) { static int tg3json__buf_append(tg3json__buffer *buf, const char *src, size_t len) {
if (len == 0) return 1; if (len == 0) return 1;
if (!tg3json__reserve_bytes((void **)&buf->data, 1, buf->len + len + 1, &buf->cap)) { if (!tg3json__reserve_bytes_parser(buf->parser, (void **)&buf->data, 1,
buf->len + len + 1, &buf->cap)) {
return 0; return 0;
} }
memcpy(buf->data + buf->len, src, len); memcpy(buf->data + buf->len, src, len);
@@ -179,7 +249,8 @@ static int tg3json__buf_append(tg3json__buffer *buf, const char *src, size_t len
} }
static int tg3json__buf_putc(tg3json__buffer *buf, char c) { static int tg3json__buf_putc(tg3json__buffer *buf, char c) {
if (!tg3json__reserve_bytes((void **)&buf->data, 1, buf->len + 2, &buf->cap)) { if (!tg3json__reserve_bytes_parser(buf->parser, (void **)&buf->data, 1,
buf->len + 2, &buf->cap)) {
return 0; return 0;
} }
buf->data[buf->len++] = c; buf->data[buf->len++] = c;
@@ -230,6 +301,7 @@ static int tg3json__parse_string_raw(tg3json__parser *parser,
tg3json__buffer buf; tg3json__buffer buf;
const char *start; const char *start;
memset(&buf, 0, sizeof(buf)); memset(&buf, 0, sizeof(buf));
buf.parser = parser;
if (parser->cur >= parser->end || *parser->cur != '"') { if (parser->cur >= parser->end || *parser->cur != '"') {
tg3json__set_error(parser, parser->cur); tg3json__set_error(parser, parser->cur);
@@ -241,6 +313,8 @@ static int tg3json__parse_string_raw(tg3json__parser *parser,
while (parser->cur < parser->end) { while (parser->cur < parser->end) {
unsigned char c = (unsigned char)*parser->cur; unsigned char c = (unsigned char)*parser->cur;
if (c == '"') { if (c == '"') {
size_t final_len = buf.len + (size_t)(parser->cur - start);
if (parser->max_string_length && final_len > parser->max_string_length) goto oom;
if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom; if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom;
++parser->cur; ++parser->cur;
*out_str = buf.data; *out_str = buf.data;
@@ -249,6 +323,8 @@ static int tg3json__parse_string_raw(tg3json__parser *parser,
} }
if (c == '\\') { if (c == '\\') {
uint32_t codepoint; uint32_t codepoint;
size_t pending_len = (size_t)(parser->cur - start);
if (parser->max_string_length && buf.len + pending_len > parser->max_string_length) goto oom;
if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom; if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom;
++parser->cur; ++parser->cur;
if (parser->cur >= parser->end) break; if (parser->cur >= parser->end) break;
@@ -304,6 +380,37 @@ oom:
static int tg3json__parse_value(tg3json__parser *parser, size_t depth, static int tg3json__parse_value(tg3json__parser *parser, size_t depth,
tg3json_value *out_value); tg3json_value *out_value);
static int tg3json__parse_int64_span(const char *start, const char *end,
int64_t *out) {
const char *p = start;
uint64_t value = 0;
uint64_t limit = (uint64_t)INT64_MAX;
int neg = 0;
if (p < end && *p == '-') {
neg = 1;
limit += 1u;
++p;
}
if (p >= end) return 0;
while (p < end) {
unsigned digit = (unsigned)(*p - '0');
if (digit > 9u) return 0;
if (value > (limit - digit) / 10u) return 0;
value = value * 10u + (uint64_t)digit;
++p;
}
if (neg) {
if (value == ((uint64_t)INT64_MAX + 1u)) {
*out = INT64_MIN;
} else {
*out = -(int64_t)value;
}
} else {
*out = (int64_t)value;
}
return 1;
}
static int tg3json__parse_array(tg3json__parser *parser, size_t depth, static int tg3json__parse_array(tg3json__parser *parser, size_t depth,
tg3json_value *out_value) { tg3json_value *out_value) {
tg3json_value *items = NULL; tg3json_value *items = NULL;
@@ -323,7 +430,8 @@ static int tg3json__parse_array(tg3json__parser *parser, size_t depth,
while (parser->cur < parser->end) { while (parser->cur < parser->end) {
tg3json_value value; tg3json_value value;
tg3json__init_value(&value); tg3json__init_value(&value);
if (!tg3json__reserve_bytes((void **)&items, sizeof(*items), count + 1, &cap)) goto oom; if (!tg3json__reserve_bytes_parser(parser, (void **)&items,
sizeof(*items), count + 1, &cap)) goto oom;
if (!tg3json__parse_value(parser, depth + 1, &value)) goto fail; if (!tg3json__parse_value(parser, depth + 1, &value)) goto fail;
items[count++] = value; items[count++] = value;
parser->cur = tg3json__skip_ws(parser->cur, parser->end); parser->cur = tg3json__skip_ws(parser->cur, parser->end);
@@ -395,14 +503,15 @@ static int tg3json__parse_object(tg3json__parser *parser, size_t depth,
free(key); free(key);
goto fail; goto fail;
} }
if (!tg3json__reserve_bytes((void **)&items, sizeof(*items), count + 1, &cap)) { if (!tg3json__reserve_bytes_parser(parser, (void **)&items,
sizeof(*items), count + 1, &cap)) {
free(key); free(key);
tg3json_value_free(&value); tg3json_value_free(&value);
goto oom; goto oom;
} }
items[count].key = key; items[count].key = key;
items[count].key_len = key_len; items[count].key_len = key_len;
items[count].value = (tg3json_value *)malloc(sizeof(tg3json_value)); items[count].value = (tg3json_value *)tg3json__parser_alloc(parser, sizeof(tg3json_value));
if (!items[count].value) { if (!items[count].value) {
free(key); free(key);
tg3json_value_free(&value); tg3json_value_free(&value);
@@ -485,29 +594,25 @@ static int tg3json__parse_number(tg3json__parser *parser, tg3json_value *out_val
do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9'); do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9');
} }
len = (size_t)(p - start);
if (len + 1 > sizeof(stack_buf)) {
num_buf = (char *)malloc(len + 1);
if (!num_buf) goto oom;
}
memcpy(num_buf, start, len);
num_buf[len] = '\0';
if (!is_real) { if (!is_real) {
char *endptr = NULL; int64_t v;
long long v; if (tg3json__parse_int64_span(start, p, &v)) {
errno = 0;
v = strtoll(num_buf, &endptr, 10);
if (errno == 0 && endptr == num_buf + len) {
out_value->type = TG3JSON_INT; out_value->type = TG3JSON_INT;
out_value->u.integer = (int64_t)v; out_value->u.integer = v;
parser->cur = p; parser->cur = p;
if (num_buf != stack_buf) free(num_buf);
return 1; return 1;
} }
is_real = 1; is_real = 1;
} }
len = (size_t)(p - start);
if (len + 1 > sizeof(stack_buf)) {
num_buf = (char *)tg3json__parser_alloc(parser, len + 1);
if (!num_buf) goto oom;
}
memcpy(num_buf, start, len);
num_buf[len] = '\0';
{ {
char *endptr = NULL; char *endptr = NULL;
errno = 0; errno = 0;
@@ -516,6 +621,7 @@ static int tg3json__parse_number(tg3json__parser *parser, tg3json_value *out_val
if (num_buf != stack_buf) free(num_buf); if (num_buf != stack_buf) free(num_buf);
goto fail; goto fail;
} }
if (parser->parse_float32) out_value->u.real = (double)(float)out_value->u.real;
out_value->type = TG3JSON_REAL; out_value->type = TG3JSON_REAL;
parser->cur = p; parser->cur = p;
if (num_buf != stack_buf) free(num_buf); if (num_buf != stack_buf) free(num_buf);
@@ -584,14 +690,25 @@ static int tg3json__parse_value(tg3json__parser *parser, size_t depth,
return 0; return 0;
} }
int tg3json_parse_n(const char *data, size_t len, size_t depth_limit, int tg3json_parse_n_opts(const char *data, size_t len,
tg3json_value *out_value, const char **out_error_pos) { const tg3json_parse_options *options,
tg3json_value *out_value,
const char **out_error_pos) {
tg3json__parser parser; tg3json__parser parser;
if (!data || !out_value) {
if (out_error_pos) *out_error_pos = data;
return 0;
}
tg3json__init_value(out_value); tg3json__init_value(out_value);
parser.cur = data; parser.cur = data;
parser.end = data + len; parser.end = data + len;
parser.error = NULL; parser.error = NULL;
parser.depth_limit = depth_limit ? depth_limit : 512; parser.depth_limit = (options && options->depth_limit) ? options->depth_limit : 512;
parser.memory_budget = options ? options->memory_budget : 0;
parser.max_single_alloc = options ? options->max_single_alloc : 0;
parser.max_string_length = options ? options->max_string_length : 0;
parser.allocated = 0;
parser.parse_float32 = options ? options->parse_float32 : 0;
if (!tg3json__parse_value(&parser, 0, out_value)) { if (!tg3json__parse_value(&parser, 0, out_value)) {
if (out_error_pos) *out_error_pos = parser.error; if (out_error_pos) *out_error_pos = parser.error;
@@ -608,6 +725,14 @@ int tg3json_parse_n(const char *data, size_t len, size_t depth_limit,
return 1; return 1;
} }
int tg3json_parse_n(const char *data, size_t len, size_t depth_limit,
tg3json_value *out_value, const char **out_error_pos) {
tg3json_parse_options options;
memset(&options, 0, sizeof(options));
options.depth_limit = depth_limit;
return tg3json_parse_n_opts(data, len, &options, out_value, out_error_pos);
}
int tg3json_parse(const char *begin, const char *end, size_t depth_limit, int tg3json_parse(const char *begin, const char *end, size_t depth_limit,
tg3json_value *out_value, const char **out_error_pos) { tg3json_value *out_value, const char **out_error_pos) {
if (!begin || !end || end < begin) { if (!begin || !end || end < begin) {