Skip to content

Commit 210302f

Browse files
authored
Merge pull request #235 from akx/depth-limit
Add optional compile-time depth limit for reader
2 parents 351b5d7 + 395e0f2 commit 210302f

File tree

4 files changed

+162
-3
lines changed

4 files changed

+162
-3
lines changed

CMakeLists.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ option(YYJSON_DISABLE_FAST_FP_CONV "Disable custom floating-point number convers
4444
option(YYJSON_DISABLE_NON_STANDARD "Disable non-standard JSON support" OFF)
4545
option(YYJSON_DISABLE_UTF8_VALIDATION "Disable UTF-8 validation" OFF)
4646
option(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS "Disable unaligned memory access explicit" OFF)
47+
option(YYJSON_READER_DEPTH_LIMIT "Set a depth limit for reading nested objects/arrays, 0 for unlimited" 0)
4748

4849
if(YYJSON_DISABLE_READER)
4950
add_definitions(-DYYJSON_DISABLE_READER)
@@ -69,7 +70,9 @@ endif()
6970
if(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS)
7071
add_definitions(-DYYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS)
7172
endif()
72-
73+
if(YYJSON_READER_DEPTH_LIMIT GREATER 0)
74+
add_definitions(-DYYJSON_READER_DEPTH_LIMIT=${YYJSON_READER_DEPTH_LIMIT})
75+
endif()
7376

7477

7578
# ------------------------------------------------------------------------------
@@ -528,6 +531,13 @@ if(YYJSON_BUILD_MISC)
528531
if(XCODE)
529532
set_default_xcode_property(make_tables)
530533
endif()
534+
535+
# depth-limit experiment; only does anything if YYJSON_READER_DEPTH_LIMIT is set
536+
add_executable(experiment_depth_limit "misc/experiment_depth_limit.c")
537+
target_link_libraries(experiment_depth_limit PRIVATE yyjson)
538+
if(XCODE)
539+
set_default_xcode_property(experiment_depth_limit)
540+
endif()
531541
endif()
532542

533543

misc/experiment_depth_limit.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include "yyjson.h"
2+
3+
#if YYJSON_READER_DEPTH_LIMIT
4+
static void make_nested_json_arrays(char *json, int depth)
5+
{
6+
char *jsonp = json;
7+
for (int i = 0; i < depth; i++) {
8+
*jsonp++ = '[';
9+
}
10+
*jsonp++ = '8';
11+
for (int i = 0; i < depth; i++) {
12+
*jsonp++ = ']';
13+
}
14+
*jsonp = 0;
15+
}
16+
static void make_nested_json_objects(char *json, int depth)
17+
{
18+
char *jsonp = json;
19+
for (int i = 0; i < depth; i++) {
20+
*jsonp++ = '{';
21+
*jsonp++ = '"';
22+
*jsonp++ = 'a' + i % 26;
23+
*jsonp++ = '"';
24+
*jsonp++ = ':';
25+
}
26+
*jsonp++ = '8';
27+
for (int i = 0; i < depth; i++) {
28+
*jsonp++ = '}';
29+
}
30+
*jsonp = 0;
31+
}
32+
33+
static int check_parse(char *json)
34+
{
35+
yyjson_read_err err;
36+
yyjson_doc *doc;
37+
yyjson_val *val;
38+
printf("Parsing: %s, depth limit = %d\n", json, YYJSON_READER_DEPTH_LIMIT);
39+
doc = yyjson_read_opts(json, strlen(json), 0, NULL, &err);
40+
yyjson_doc_free(doc);
41+
printf("=> Error code: %d\n", err.code);
42+
if (err.code != YYJSON_READ_ERROR_DEPTH)
43+
{
44+
printf("Expected depth error, but didn't get one!");
45+
return 1;
46+
}
47+
return 0;
48+
}
49+
50+
int main()
51+
{
52+
char json[512];
53+
make_nested_json_arrays(json, YYJSON_READER_DEPTH_LIMIT + 1);
54+
check_parse(json);
55+
make_nested_json_objects(json, YYJSON_READER_DEPTH_LIMIT + 1);
56+
check_parse(json);
57+
return 0;
58+
}
59+
#else
60+
int main()
61+
{
62+
printf("Library not compiled with depth limit support.\n");
63+
return 0;
64+
}
65+
#endif

src/yyjson.c

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
# pragma warning(disable:4101) /* unreferenced variable */
4747
# pragma warning(disable:4102) /* unreferenced label */
4848
# pragma warning(disable:4127) /* conditional expression is constant */
49+
# pragma warning(disable:4702) /* unreachable code */
4950
# pragma warning(disable:4706) /* assignment within conditional expression */
5051
#endif
5152

@@ -338,8 +339,9 @@ uint32_t yyjson_version(void) {
338339
#ifndef YYJSON_DISABLE_UTF8_VALIDATION
339340
#define YYJSON_DISABLE_UTF8_VALIDATION 0
340341
#endif
341-
342-
342+
#ifndef YYJSON_READER_DEPTH_LIMIT
343+
#define YYJSON_READER_DEPTH_LIMIT 0
344+
#endif
343345

344346
/*==============================================================================
345347
* MARK: - Macros (Private)
@@ -438,6 +440,7 @@ uint32_t yyjson_version(void) {
438440
#define MSG_ERR_UTF8 "invalid utf-8 encoding in string"
439441
#define MSG_ERR_UTF16 "UTF-16 encoding is not supported"
440442
#define MSG_ERR_UTF32 "UTF-32 encoding is not supported"
443+
#define MSG_DEPTH "depth limit exceeded"
441444

442445
/* U64 constant values */
443446
#undef U64_MAX
@@ -5306,6 +5309,7 @@ fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N);
53065309
fail_character: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR);
53075310
fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT);
53085311
fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);
5312+
fail_depth: return_err(cur, DEPTH, MSG_DEPTH);
53095313

53105314
#undef return_err
53115315
}
@@ -5366,6 +5370,10 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof,
53665370
u8 *raw_ptr = raw_end;
53675371
u8 **pre = &raw_ptr; /* previous raw end pointer */
53685372

5373+
#if YYJSON_READER_DEPTH_LIMIT
5374+
u32 container_depth = 0; /* current array/object depth */
5375+
#endif
5376+
53695377
dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur);
53705378
hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val);
53715379
hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0;
@@ -5391,6 +5399,12 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof,
53915399
}
53925400

53935401
arr_begin:
5402+
#if YYJSON_READER_DEPTH_LIMIT
5403+
container_depth++;
5404+
if (unlikely(container_depth >= YYJSON_READER_DEPTH_LIMIT)) {
5405+
goto fail_depth;
5406+
}
5407+
#endif
53945408
/* save current container */
53955409
ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |
53965410
(ctn->tag & YYJSON_TAG_MASK);
@@ -5496,6 +5510,9 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof,
54965510
goto fail_character_arr_end;
54975511

54985512
arr_end:
5513+
#if YYJSON_READER_DEPTH_LIMIT
5514+
container_depth--;
5515+
#endif
54995516
/* get parent container */
55005517
ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);
55015518

@@ -5514,6 +5531,12 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof,
55145531
}
55155532

55165533
obj_begin:
5534+
#if YYJSON_READER_DEPTH_LIMIT
5535+
container_depth++;
5536+
if (unlikely(container_depth >= YYJSON_READER_DEPTH_LIMIT)) {
5537+
goto fail_depth;
5538+
}
5539+
#endif
55175540
/* push container */
55185541
ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |
55195542
(ctn->tag & YYJSON_TAG_MASK);
@@ -5660,6 +5683,9 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof,
56605683
goto fail_character_obj_end;
56615684

56625685
obj_end:
5686+
#if YYJSON_READER_DEPTH_LIMIT
5687+
container_depth--;
5688+
#endif
56635689
/* pop container */
56645690
ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);
56655691
/* point to the next value */
@@ -5709,6 +5735,7 @@ fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP);
57095735
fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END);
57105736
fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT);
57115737
fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);
5738+
fail_depth: return_err(cur, DEPTH, MSG_DEPTH);
57125739

57135740
#undef val_incr
57145741
#undef return_err
@@ -5769,6 +5796,9 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof,
57695796
u8 raw_end[1]; /* raw end for null-terminator */
57705797
u8 *raw_ptr = raw_end;
57715798
u8 **pre = &raw_ptr; /* previous raw end pointer */
5799+
#if YYJSON_READER_DEPTH_LIMIT
5800+
u32 container_depth = 0; /* current array/object depth */
5801+
#endif
57725802

57735803
dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur);
57745804
hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val);
@@ -5797,6 +5827,13 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof,
57975827
}
57985828

57995829
arr_begin:
5830+
#if YYJSON_READER_DEPTH_LIMIT
5831+
container_depth++;
5832+
if (unlikely(container_depth >= YYJSON_READER_DEPTH_LIMIT)) {
5833+
goto fail_depth;
5834+
}
5835+
#endif
5836+
58005837
/* save current container */
58015838
ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |
58025839
(ctn->tag & YYJSON_TAG_MASK);
@@ -5919,6 +5956,9 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof,
59195956
goto fail_character_arr_end;
59205957

59215958
arr_end:
5959+
#if YYJSON_READER_DEPTH_LIMIT
5960+
container_depth--;
5961+
#endif
59225962
/* get parent container */
59235963
ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);
59245964

@@ -5938,6 +5978,13 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof,
59385978
}
59395979

59405980
obj_begin:
5981+
#if YYJSON_READER_DEPTH_LIMIT
5982+
container_depth++;
5983+
if (unlikely(container_depth >= YYJSON_READER_DEPTH_LIMIT)) {
5984+
goto fail_depth;
5985+
}
5986+
#endif
5987+
59415988
/* push container */
59425989
ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |
59435990
(ctn->tag & YYJSON_TAG_MASK);
@@ -6104,6 +6151,10 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof,
61046151
goto fail_character_obj_end;
61056152

61066153
obj_end:
6154+
#if YYJSON_READER_DEPTH_LIMIT
6155+
container_depth--;
6156+
#endif
6157+
61076158
/* pop container */
61086159
ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);
61096160
/* point to the next value */
@@ -6154,6 +6205,7 @@ fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP);
61546205
fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END);
61556206
fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT);
61566207
fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);
6208+
fail_depth: return_err(cur, DEPTH, MSG_DEPTH);
61576209

61586210
#undef val_incr
61596211
#undef return_err
@@ -6612,6 +6664,10 @@ yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len,
66126664
u8 **con = NULL; /* for incremental string parsing */
66136665
u8 saved_end = '\0'; /* saved end char */
66146666

6667+
#if YYJSON_READER_DEPTH_LIMIT
6668+
u32 container_depth = 0; /* current array/object depth */
6669+
#endif
6670+
66156671
/* validate input parameters */
66166672
if (!err) err = &tmp_err;
66176673
if (unlikely(!state)) {
@@ -6733,6 +6789,13 @@ yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len,
67336789
return_err(cur, UNEXPECTED_CHARACTER, msg);
67346790

67356791
arr_begin:
6792+
#if YYJSON_READER_DEPTH_LIMIT
6793+
container_depth++;
6794+
if (unlikely(container_depth >= YYJSON_READER_DEPTH_LIMIT)) {
6795+
goto fail_depth;
6796+
}
6797+
#endif
6798+
67366799
/* save current container */
67376800
ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |
67386801
(ctn->tag & YYJSON_TAG_MASK);
@@ -6822,6 +6885,9 @@ yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len,
68226885
goto fail_character_arr_end;
68236886

68246887
arr_end:
6888+
#if YYJSON_READER_DEPTH_LIMIT
6889+
container_depth--;
6890+
#endif
68256891
/* get parent container */
68266892
ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);
68276893

@@ -6840,6 +6906,13 @@ yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len,
68406906
}
68416907

68426908
obj_begin:
6909+
#if YYJSON_READER_DEPTH_LIMIT
6910+
container_depth++;
6911+
if (unlikely(container_depth >= YYJSON_READER_DEPTH_LIMIT)) {
6912+
goto fail_depth;
6913+
}
6914+
#endif
6915+
68436916
/* push container */
68446917
ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) |
68456918
(ctn->tag & YYJSON_TAG_MASK);
@@ -6954,6 +7027,10 @@ yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len,
69547027
goto fail_character_obj_end;
69557028

69567029
obj_end:
7030+
#if YYJSON_READER_DEPTH_LIMIT
7031+
container_depth--;
7032+
#endif
7033+
69577034
/* pop container */
69587035
ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs);
69597036
/* point to the next value */
@@ -7020,6 +7097,7 @@ fail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY);
70207097
fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP);
70217098
fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END);
70227099
fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE);
7100+
fail_depth: return_err(cur, DEPTH, MSG_DEPTH);
70237101

70247102
#undef val_incr
70257103
#undef return_err

src/yyjson.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@
142142
#ifndef YYJSON_HAS_STDBOOL_H
143143
#endif
144144

145+
/* Define to an integer to set a depth limit for containers (arrays, objects). */
146+
#ifndef YYJSON_READER_DEPTH_LIMIT
147+
#endif
145148

146149

147150
/*==============================================================================
@@ -875,6 +878,9 @@ static const yyjson_read_code YYJSON_READ_ERROR_FILE_READ = 13;
875878
/** Incomplete input during incremental parsing; parsing state is preserved. */
876879
static const yyjson_read_code YYJSON_READ_ERROR_MORE = 14;
877880

881+
/** Read depth limit exceeded. */
882+
static const yyjson_read_code YYJSON_READ_ERROR_DEPTH = 15;
883+
878884
/** Error information for JSON reader. */
879885
typedef struct yyjson_read_err {
880886
/** Error code, see `yyjson_read_code` for all possible values. */

0 commit comments

Comments
 (0)