json 2.18.1 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb2890db4c527125d27bc7c21fc64d3ac532ffbec8080f89a678daf48c36e09e
4
- data.tar.gz: c4b37d085d05d3c43df97b3c24898dc6be61c76ba64c749b5a8a86bf4fc1198d
3
+ metadata.gz: 24e3bc40d587f5a4c001b5f4c7da30170bdf06f5401f2aa1928469c8fb3860e2
4
+ data.tar.gz: b668789b5f3a56d7db311eb4ec9d10b6d236debab23016fabd1682316d50ae50
5
5
  SHA512:
6
- metadata.gz: fb55ef5a0aa6961ef0fe3bb30f398834820357045ad27a8fdb7e53eaba3af7c4d356ef26c0e73b7a87d2d9d51e500eae7193d7d1ae3aa1058c7973bcc462674b
7
- data.tar.gz: bfb499789bbcee7f5f8d67e32ded664dc62c632ae39fe80bf4bff3d6aec16eee3730a7c4883216a393326f2ab33e94e5f9c58da4c5b31627347108c36c2b211c
6
+ metadata.gz: aaa62a65724e3caa24797ce93ed3ac710a0186a1fd2db990a20fddb10eb99c71d92a967d0bed9d5086cb5b203c5dda0793faa461fda1e5c6963c62da5b4db3dc
7
+ data.tar.gz: fc44e571dcb662a35a36165d7f7afbb94b11ab8d5660ab1a65905961b7548260152afbb3286fd233223ea5435b6bb62135a3b67b30bd26ca0cd8114e6921c980
data/CHANGES.md CHANGED
@@ -2,6 +2,63 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2026-06-23 (2.20.0)
6
+
7
+ * Both C and Java parsers are no longer recursive, so parsing very deep documents with `max_nesting: false` will no longer
8
+ result in `SystemStackError stack level too deep` errors.
9
+ * The `:max_nesting` option still defaults to `100`.
10
+ * Optimized floating point number parsing further by replacing the ryu algorithm by a port of Eisel-Lemire Fast Float.
11
+ * Added `JSON::ResumableParser` to parse streams of JSON documents. Not yet available on JRuby.
12
+ * Deprecate default support of JavaScript comments in the parser and add `allow_comments: true` parsing option.
13
+ * Integrate with Ruby 4.1 `ruby_sized_xfree`.
14
+
15
+ ### 2026-06-11 (2.19.9)
16
+
17
+ * Fix buffer overflow that could lead to a crash when writing JSON directly into an IO
18
+ with `JSON.generate(object, io)`. [CVE-2026-54696].
19
+
20
+ ### 2026-06-03 (2.19.8)
21
+
22
+ * Fix 1-byte buffer overread on EOS errors.
23
+ * Handle invalid types passed as `max_nesting` option.
24
+
25
+ ### 2026-05-28 (2.19.7)
26
+
27
+ * Fix some more edge cases with out of range floats.
28
+ * Ensure the string provided to `JSON.parse` can't be mutated during parsing.
29
+ * Add missing write barriers in `State#dup`.
30
+ * Further validate generator `depth` config.
31
+
32
+ ### 2026-05-28 (2.19.6)
33
+
34
+ * Cleanly handle overly large `depth` generator argument.
35
+ * Add missing write barrier in `ParserConfig`.
36
+
37
+ ### 2026-05-04 (2.19.5)
38
+
39
+ * Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful.
40
+
41
+ ### 2026-04-19 (2.19.4)
42
+
43
+ * Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`).
44
+
45
+ ### 2026-03-25 (2.19.3)
46
+
47
+ * Fix handling of unescaped control characters preceeded by a backslash.
48
+
49
+ ### 2026-03-18 (2.19.2)
50
+
51
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
52
+
53
+ ### 2026-03-08 (2.19.1)
54
+
55
+ * Fix a compiler dependent GC bug introduced in `2.18.0`.
56
+
57
+ ### 2026-03-06 (2.19.0)
58
+
59
+ * Fix `allow_blank` parsing option to no longer allow invalid types (e.g. `load([], allow_blank: true)` now raise a type error).
60
+ * Add `allow_invalid_escape` parsing option to ignore backslashes that aren't followed by one of the valid escape characters.
61
+
5
62
  ### 2026-02-03 (2.18.1)
6
63
 
7
64
  * Fix a potential crash in very specific circumstance if GC triggers during a call to `to_json`
@@ -11,6 +68,10 @@
11
68
 
12
69
  * Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
13
70
 
71
+ ### 2026-03-18 (2.17.1.2) - Security Backport
72
+
73
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
74
+
14
75
  ### 2025-12-04 (2.17.1)
15
76
 
16
77
  * Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
@@ -37,6 +98,10 @@
37
98
  * Optimized numbers parsing using SWAR (thanks to Scott Myron).
38
99
  * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
39
100
 
101
+ ### 2026-03-18 (2.15.2.1) - Security Backport
102
+
103
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
104
+
40
105
  ### 2025-10-25 (2.15.2)
41
106
 
42
107
  * Fix `JSON::Coder` to have one dedicated depth counter per invocation.
data/LEGAL CHANGED
@@ -15,6 +15,6 @@ ext/json/ext/vendor/jeaiii-ltoa.h::
15
15
  This file is adapted from https://github.com/jeaiii/itoa
16
16
  It is licensed under the MIT License
17
17
 
18
- ext/json/ext/vendor/ryu.h::
19
- This file is adapted from the Ryu algorithm by Ulf Adams https://github.com/ulfjack/ryu.
20
- It is dual-licensed under Apache License 2.0 OR Boost Software License 1.0.
18
+ ext/json/ext/vendor/fast_float_parser.h::
19
+ This file is adapted from the Fast Float C++ library by The fast_float authors https://github.com/fastfloat/fast_float
20
+ It is licensed under the MIT License
data/README.md CHANGED
@@ -249,6 +249,17 @@ There are also the methods `Kernel#j` for generate, and `Kernel#jj` for
249
249
  `pretty_generate` output to the console, that work analogous to Core Ruby's `p` and
250
250
  the `pp` library's `pp` methods.
251
251
 
252
+ ## Security
253
+
254
+ When parsing or serializing untrusted input, parser and generator options should never be user controlled.
255
+
256
+ ```ruby
257
+ # Dangerous, DO NOT DO THIS.
258
+ JSON.generate(params[:data], params[:options])
259
+ ```
260
+
261
+ Security vulnerability reports relying on attacker controlled parsing or generator options will be handled as regular bug fixes.
262
+
252
263
  ## Development
253
264
 
254
265
  ### Prerequisites
@@ -295,5 +306,3 @@ The latest version of this library can be downloaded at
295
306
  Online Documentation should be located at
296
307
 
297
308
  * https://www.rubydoc.info/gems/json
298
-
299
- [Ragel]: http://www.colm.net/open-source/ragel/
@@ -11,11 +11,11 @@ enum fbuffer_type {
11
11
 
12
12
  typedef struct FBufferStruct {
13
13
  enum fbuffer_type type;
14
- unsigned long initial_length;
15
- unsigned long len;
16
- unsigned long capa;
14
+ size_t initial_length;
15
+ size_t len;
16
+ size_t capa;
17
17
  #if JSON_DEBUG
18
- unsigned long requested;
18
+ size_t requested;
19
19
  #endif
20
20
  char *ptr;
21
21
  VALUE io;
@@ -32,25 +32,29 @@ typedef struct FBufferStruct {
32
32
 
33
33
  static void fbuffer_free(FBuffer *fb);
34
34
  static void fbuffer_clear(FBuffer *fb);
35
- static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len);
35
+ static void fbuffer_append(FBuffer *fb, const char *newstr, size_t len);
36
36
  static void fbuffer_append_long(FBuffer *fb, long number);
37
37
  static inline void fbuffer_append_char(FBuffer *fb, char newchr);
38
38
  static VALUE fbuffer_finalize(FBuffer *fb);
39
39
 
40
- static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size)
40
+ static void fbuffer_init(FBuffer *fb, size_t initial_length, VALUE io, char *stack_buffer, size_t stack_buffer_size)
41
41
  {
42
- fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
43
- if (stack_buffer) {
42
+ if (RTEST(io)) {
43
+ JSON_ASSERT(fb->type == FBUFFER_HEAP_ALLOCATED);
44
+ fb->io = io;
45
+ fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_IO_BUFFER_SIZE;
46
+ } else {
44
47
  fb->type = FBUFFER_STACK_ALLOCATED;
45
48
  fb->ptr = stack_buffer;
46
49
  fb->capa = stack_buffer_size;
50
+ fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
47
51
  }
48
52
  #if JSON_DEBUG
49
53
  fb->requested = 0;
50
54
  #endif
51
55
  }
52
56
 
53
- static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed)
57
+ static inline void fbuffer_consumed(FBuffer *fb, size_t consumed)
54
58
  {
55
59
  #if JSON_DEBUG
56
60
  if (consumed > fb->requested) {
@@ -64,7 +68,7 @@ static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed)
64
68
  static void fbuffer_free(FBuffer *fb)
65
69
  {
66
70
  if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) {
67
- ruby_xfree(fb->ptr);
71
+ JSON_SIZED_FREE_N(fb->ptr, fb->capa);
68
72
  }
69
73
  }
70
74
 
@@ -79,48 +83,43 @@ static void fbuffer_flush(FBuffer *fb)
79
83
  fbuffer_clear(fb);
80
84
  }
81
85
 
82
- static void fbuffer_realloc(FBuffer *fb, unsigned long required)
86
+ static void fbuffer_realloc(FBuffer *fb, size_t new_capa)
83
87
  {
84
- if (required > fb->capa) {
88
+ if (new_capa > fb->capa) {
85
89
  if (fb->type == FBUFFER_STACK_ALLOCATED) {
86
90
  const char *old_buffer = fb->ptr;
87
- fb->ptr = ALLOC_N(char, required);
91
+ fb->ptr = ALLOC_N(char, new_capa);
88
92
  fb->type = FBUFFER_HEAP_ALLOCATED;
89
93
  MEMCPY(fb->ptr, old_buffer, char, fb->len);
90
94
  } else {
91
- REALLOC_N(fb->ptr, char, required);
95
+ JSON_SIZED_REALLOC_N(fb->ptr, char, new_capa, fb->capa);
92
96
  }
93
- fb->capa = required;
97
+ fb->capa = new_capa;
94
98
  }
95
99
  }
96
100
 
97
- static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested)
101
+ static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested)
98
102
  {
99
103
  if (RB_UNLIKELY(fb->io)) {
100
- if (fb->capa < FBUFFER_IO_BUFFER_SIZE) {
101
- fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
102
- } else {
104
+ if (fb->capa != 0) {
103
105
  fbuffer_flush(fb);
104
- }
105
-
106
- if (RB_LIKELY(requested < fb->capa)) {
107
- return;
106
+ if (RB_LIKELY(requested < fb->capa)) {
107
+ return;
108
+ }
108
109
  }
109
110
  }
110
111
 
111
- unsigned long required;
112
+ size_t new_capa = fb->capa ? fb->capa : fb->initial_length;
113
+ size_t needed_capa = requested + fb->len;
112
114
 
113
- if (RB_UNLIKELY(!fb->ptr)) {
114
- fb->ptr = ALLOC_N(char, fb->initial_length);
115
- fb->capa = fb->initial_length;
115
+ while (new_capa < needed_capa) {
116
+ new_capa *= 2;
116
117
  }
117
118
 
118
- for (required = fb->capa; requested > required - fb->len; required <<= 1);
119
-
120
- fbuffer_realloc(fb, required);
119
+ fbuffer_realloc(fb, new_capa);
121
120
  }
122
121
 
123
- static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
122
+ static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
124
123
  {
125
124
  #if JSON_DEBUG
126
125
  fb->requested = requested;
@@ -131,13 +130,22 @@ static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
131
130
  }
132
131
  }
133
132
 
134
- static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, unsigned long len)
133
+ static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b)
134
+ {
135
+ size_t result = a * b;
136
+ if (RB_UNLIKELY(a != 0 && (result / a) != b)) {
137
+ rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated");
138
+ }
139
+ return result;
140
+ }
141
+
142
+ static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len)
135
143
  {
136
144
  MEMCPY(fb->ptr + fb->len, newstr, char, len);
137
145
  fbuffer_consumed(fb, len);
138
146
  }
139
147
 
140
- static inline void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len)
148
+ static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len)
141
149
  {
142
150
  if (len > 0) {
143
151
  fbuffer_inc_capa(fb, len);
@@ -162,19 +170,20 @@ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr)
162
170
  static void fbuffer_append_str(FBuffer *fb, VALUE str)
163
171
  {
164
172
  const char *ptr;
165
- unsigned long len;
173
+ size_t len;
166
174
  RSTRING_GETMEM(str, ptr, len);
167
175
 
168
176
  fbuffer_append(fb, ptr, len);
177
+ RB_GC_GUARD(str);
169
178
  }
170
179
 
171
180
  static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
172
181
  {
173
182
  const char *ptr;
174
- unsigned long len;
183
+ size_t len;
175
184
  RSTRING_GETMEM(str, ptr, len);
176
185
 
177
- fbuffer_inc_capa(fb, repeat * len);
186
+ fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
178
187
  while (repeat) {
179
188
  #if JSON_DEBUG
180
189
  fb->requested = len;
@@ -182,6 +191,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
182
191
  fbuffer_append_reserved(fb, ptr, len);
183
192
  repeat--;
184
193
  }
194
+ RB_GC_GUARD(str);
185
195
  }
186
196
 
187
197
  static inline void fbuffer_append_char(FBuffer *fb, char newchr)
@@ -5,6 +5,9 @@ if RUBY_ENGINE == 'truffleruby'
5
5
  File.write('Makefile', dummy_makefile("").join)
6
6
  else
7
7
  append_cflags("-std=c99")
8
+ have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
9
+ have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1
10
+
8
11
  $defs << "-DJSON_GENERATOR"
9
12
  $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
10
13