json 2.19.2 → 2.19.4

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: 747237eb2b9348d361e6e93684f81381b4f0dcf0cd36971bc809ac042ce295bc
4
- data.tar.gz: 1c6243010258fd2077acf63c5b372babce9a32e789630279bc8b129fc2deef5d
3
+ metadata.gz: 48a111e63867f6bb2c0cb1b2740a898f3102bc07ac196136e26ca9b68f510f89
4
+ data.tar.gz: 2f760d8afae80555c483afaaa8bead26f045d26c394cc07843a85deaff7fdd40
5
5
  SHA512:
6
- metadata.gz: b43b4ca3d570a3c4051a319f9eb2d2807a6b2567f43cedf8bc21d8208289a3f3a275dc650353cd6ef4bd3e2022afcf73f17164fda51081134e11ac5172374459
7
- data.tar.gz: 82a96b04fa36bb5b0ab72868d67e95cfcc8cc8d3f0a045a1caf8045b090e5cf46647b664accf7c657073020847cd8ce6ad28535d14536e214dcaab21b6aa4c17
6
+ metadata.gz: 6387b2c483c8f4d7ed304f33a3553a2fde78c04efeff7ae32a1e952380df92d6df87b543c22832907b007f758f09bd2f80ef4db0b5753e46fdf73f0e264e9e02
7
+ data.tar.gz: c3c855b9e460768f83516914580c10854ea2b28ee73976e311ce55a0d2265576f65a23c41cccc47c7443d79883658962d1cf923880685d555b3af1ba0ebcb056
data/CHANGES.md CHANGED
@@ -2,9 +2,17 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
- ### 2026-03-08 (2.19.2)
5
+ ### 2026-04-19 (2.19.4)
6
6
 
7
- * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`.
7
+ * Fix parsing of out of range floats (very large exponents that lead ot either `0.0` or `Inf`).
8
+
9
+ ### 2026-03-25 (2.19.3)
10
+
11
+ * Fix handling of unescaped control characters preceeded by a backslash.
12
+
13
+ ### 2026-03-18 (2.19.2)
14
+
15
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
8
16
 
9
17
  ### 2026-03-08 (2.19.1)
10
18
 
@@ -24,6 +32,10 @@
24
32
 
25
33
  * Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
26
34
 
35
+ ### 2026-03-18 (2.17.1.2) - Security Backport
36
+
37
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
38
+
27
39
  ### 2025-12-04 (2.17.1)
28
40
 
29
41
  * Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
@@ -50,6 +62,10 @@
50
62
  * Optimized numbers parsing using SWAR (thanks to Scott Myron).
51
63
  * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
52
64
 
65
+ ### 2026-03-18 (2.15.2.1) - Security Backport
66
+
67
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
68
+
53
69
  ### 2025-10-25 (2.15.2)
54
70
 
55
71
  * Fix `JSON::Coder` to have one dedicated depth counter per invocation.
@@ -5,6 +5,8 @@ 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
+
8
10
  $defs << "-DJSON_GENERATOR"
9
11
  $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
10
12
 
@@ -722,27 +722,20 @@ static void State_compact(void *ptr)
722
722
  state->as_json = rb_gc_location(state->as_json);
723
723
  }
724
724
 
725
- static void State_free(void *ptr)
726
- {
727
- JSON_Generator_State *state = ptr;
728
- ruby_xfree(state);
729
- }
730
-
731
725
  static size_t State_memsize(const void *ptr)
732
726
  {
733
727
  return sizeof(JSON_Generator_State);
734
728
  }
735
729
 
736
730
  static const rb_data_type_t JSON_Generator_State_type = {
737
- "JSON/Generator/State",
738
- {
731
+ .wrap_struct_name = "JSON/Generator/State",
732
+ .function = {
739
733
  .dmark = State_mark,
740
- .dfree = State_free,
734
+ .dfree = RUBY_DEFAULT_FREE,
741
735
  .dsize = State_memsize,
742
736
  .dcompact = State_compact,
743
737
  },
744
- 0, 0,
745
- RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
738
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
746
739
  };
747
740
 
748
741
  static void state_init(JSON_Generator_State *state)
data/ext/json/ext/json.h CHANGED
@@ -54,6 +54,17 @@ typedef unsigned char _Bool;
54
54
  # define RUBY_TYPED_FROZEN_SHAREABLE 0
55
55
  #endif
56
56
 
57
+ #ifdef RUBY_TYPED_EMBEDDABLE
58
+ # define HAVE_RUBY_TYPED_EMBEDDABLE 1
59
+ #else
60
+ # ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE
61
+ # define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE
62
+ # define HAVE_RUBY_TYPED_EMBEDDABLE 1
63
+ # else
64
+ # define RUBY_TYPED_EMBEDDABLE 0
65
+ # endif
66
+ #endif
67
+
57
68
  #ifndef NORETURN
58
69
  #if defined(__has_attribute) && __has_attribute(noreturn)
59
70
  #define NORETURN(x) __attribute__((noreturn)) x
@@ -7,6 +7,10 @@ have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
7
7
  have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2
8
8
  have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby
9
9
 
10
+ if RUBY_ENGINE == "ruby"
11
+ have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
12
+ end
13
+
10
14
  append_cflags("-std=c99")
11
15
 
12
16
  if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
@@ -241,17 +241,27 @@ static void rvalue_stack_mark(void *ptr)
241
241
  {
242
242
  rvalue_stack *stack = (rvalue_stack *)ptr;
243
243
  long index;
244
- for (index = 0; index < stack->head; index++) {
245
- rb_gc_mark(stack->ptr[index]);
244
+ if (stack && stack->ptr) {
245
+ for (index = 0; index < stack->head; index++) {
246
+ rb_gc_mark(stack->ptr[index]);
247
+ }
246
248
  }
247
249
  }
248
250
 
251
+ static void rvalue_stack_free_buffer(rvalue_stack *stack)
252
+ {
253
+ ruby_xfree(stack->ptr);
254
+ stack->ptr = NULL;
255
+ }
256
+
249
257
  static void rvalue_stack_free(void *ptr)
250
258
  {
251
259
  rvalue_stack *stack = (rvalue_stack *)ptr;
252
260
  if (stack) {
253
- ruby_xfree(stack->ptr);
261
+ rvalue_stack_free_buffer(stack);
262
+ #ifndef HAVE_RUBY_TYPED_EMBEDDABLE
254
263
  ruby_xfree(stack);
264
+ #endif
255
265
  }
256
266
  }
257
267
 
@@ -262,14 +272,13 @@ static size_t rvalue_stack_memsize(const void *ptr)
262
272
  }
263
273
 
264
274
  static const rb_data_type_t JSON_Parser_rvalue_stack_type = {
265
- "JSON::Ext::Parser/rvalue_stack",
266
- {
275
+ .wrap_struct_name = "JSON::Ext::Parser/rvalue_stack",
276
+ .function = {
267
277
  .dmark = rvalue_stack_mark,
268
278
  .dfree = rvalue_stack_free,
269
279
  .dsize = rvalue_stack_memsize,
270
280
  },
271
- 0, 0,
272
- RUBY_TYPED_FREE_IMMEDIATELY,
281
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE,
273
282
  };
274
283
 
275
284
  static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref)
@@ -291,8 +300,12 @@ static void rvalue_stack_eagerly_release(VALUE handle)
291
300
  if (handle) {
292
301
  rvalue_stack *stack;
293
302
  TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
294
- RTYPEDDATA_DATA(handle) = NULL;
303
+ #ifdef HAVE_RUBY_TYPED_EMBEDDABLE
304
+ rvalue_stack_free_buffer(stack);
305
+ #else
295
306
  rvalue_stack_free(stack);
307
+ RTYPEDDATA_DATA(handle) = NULL;
308
+ #endif
296
309
  }
297
310
  }
298
311
 
@@ -343,7 +356,7 @@ typedef struct JSON_ParserStruct {
343
356
  } JSON_ParserConfig;
344
357
 
345
358
  typedef struct JSON_ParserStateStruct {
346
- VALUE stack_handle;
359
+ VALUE *stack_handle;
347
360
  const char *start;
348
361
  const char *cursor;
349
362
  const char *end;
@@ -436,9 +449,8 @@ static VALUE build_parse_error_message(const char *format, JSON_ParserState *sta
436
449
  }
437
450
  }
438
451
 
439
- VALUE msg = rb_sprintf(format, ptr);
440
- VALUE message = rb_enc_sprintf(enc_utf8, "%s at line %ld column %ld", RSTRING_PTR(msg), line, column);
441
- RB_GC_GUARD(msg);
452
+ VALUE message = rb_enc_sprintf(enc_utf8, format, ptr);
453
+ rb_str_catf(message, " at line %ld column %ld", line, column);
442
454
  return message;
443
455
  }
444
456
 
@@ -758,7 +770,9 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
758
770
  }
759
771
  raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1);
760
772
  }
761
- } else if (config->allow_invalid_escape) {
773
+ }
774
+
775
+ if (config->allow_invalid_escape) {
762
776
  APPEND_CHAR(*pe);
763
777
  } else {
764
778
  raise_parse_error_at("invalid escape character in string: %s", state, pe - 1);
@@ -841,7 +855,7 @@ NOINLINE(static) VALUE json_decode_large_float(const char *start, long len)
841
855
  /* Ruby JSON optimized float decoder using vendored Ryu algorithm
842
856
  * Accepts pre-extracted mantissa and exponent from first-pass validation
843
857
  */
844
- static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int32_t exponent, bool negative,
858
+ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative,
845
859
  const char *start, const char *end)
846
860
  {
847
861
  if (RB_UNLIKELY(config->decimal_class)) {
@@ -849,13 +863,21 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis
849
863
  return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
850
864
  }
851
865
 
866
+ if (RB_UNLIKELY(exponent > INT32_MAX)) {
867
+ return negative ? CMinusInfinity : CInfinity;
868
+ }
869
+
870
+ if (RB_UNLIKELY(exponent < INT32_MIN)) {
871
+ return rb_float_new(negative ? -0.0 : 0.0);
872
+ }
873
+
852
874
  // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case)
853
875
  // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308)
854
876
  if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) {
855
877
  return json_decode_large_float(start, end - start);
856
878
  }
857
879
 
858
- return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, exponent, negative));
880
+ return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, (int32_t)exponent, negative));
859
881
  }
860
882
 
861
883
  static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count)
@@ -909,9 +931,6 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
909
931
  cursor_position(state, &line, &column);
910
932
  rb_str_concat(message, build_parse_error_message("", state, line, column)) ;
911
933
  rb_exc_raise(parse_error_new(message, line, column));
912
-
913
- raise_parse_error(RSTRING_PTR(message), state);
914
- RB_GC_GUARD(message);
915
934
  }
916
935
 
917
936
  static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
@@ -948,7 +967,7 @@ static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *
948
967
  if (RB_UNLIKELY(config->on_load_proc)) {
949
968
  value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil);
950
969
  }
951
- rvalue_stack_push(state->stack, value, &state->stack_handle, &state->stack);
970
+ rvalue_stack_push(state->stack, value, state->stack_handle, &state->stack);
952
971
  return value;
953
972
  }
954
973
 
@@ -1133,7 +1152,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1133
1152
  const char first_digit = *state->cursor;
1134
1153
 
1135
1154
  // Variables for Ryu optimization - extract digits during parsing
1136
- int32_t exponent = 0;
1155
+ int64_t exponent = 0;
1137
1156
  int decimal_point_pos = -1;
1138
1157
  uint64_t mantissa = 0;
1139
1158
 
@@ -1177,7 +1196,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1177
1196
  raise_parse_error_at("invalid number: %s", state, start);
1178
1197
  }
1179
1198
 
1180
- exponent = negative_exponent ? -((int32_t)abs_exponent) : ((int32_t)abs_exponent);
1199
+ exponent = negative_exponent ? -abs_exponent : abs_exponent;
1181
1200
  }
1182
1201
 
1183
1202
  if (integer) {
@@ -1558,11 +1577,13 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1558
1577
  const char *start;
1559
1578
  RSTRING_GETMEM(Vsource, start, len);
1560
1579
 
1580
+ VALUE stack_handle = 0;
1561
1581
  JSON_ParserState _state = {
1562
1582
  .start = start,
1563
1583
  .cursor = start,
1564
1584
  .end = start + len,
1565
1585
  .stack = &stack,
1586
+ .stack_handle = &stack_handle,
1566
1587
  };
1567
1588
  JSON_ParserState *state = &_state;
1568
1589
 
@@ -1570,8 +1591,8 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1570
1591
 
1571
1592
  // This may be skipped in case of exception, but
1572
1593
  // it won't cause a leak.
1573
- rvalue_stack_eagerly_release(state->stack_handle);
1574
-
1594
+ rvalue_stack_eagerly_release(stack_handle);
1595
+ RB_GC_GUARD(stack_handle);
1575
1596
  json_ensure_eof(state);
1576
1597
 
1577
1598
  return result;
@@ -1609,26 +1630,19 @@ static void JSON_ParserConfig_mark(void *ptr)
1609
1630
  rb_gc_mark(config->decimal_class);
1610
1631
  }
1611
1632
 
1612
- static void JSON_ParserConfig_free(void *ptr)
1613
- {
1614
- JSON_ParserConfig *config = ptr;
1615
- ruby_xfree(config);
1616
- }
1617
-
1618
1633
  static size_t JSON_ParserConfig_memsize(const void *ptr)
1619
1634
  {
1620
1635
  return sizeof(JSON_ParserConfig);
1621
1636
  }
1622
1637
 
1623
1638
  static const rb_data_type_t JSON_ParserConfig_type = {
1624
- "JSON::Ext::Parser/ParserConfig",
1625
- {
1639
+ .wrap_struct_name = "JSON::Ext::Parser/ParserConfig",
1640
+ .function = {
1626
1641
  JSON_ParserConfig_mark,
1627
- JSON_ParserConfig_free,
1642
+ RUBY_DEFAULT_FREE,
1628
1643
  JSON_ParserConfig_memsize,
1629
1644
  },
1630
- 0, 0,
1631
- RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
1645
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
1632
1646
  };
1633
1647
 
1634
1648
  static VALUE cJSON_parser_s_allocate(VALUE klass)
@@ -48,7 +48,7 @@ module JSON
48
48
  SCRIPT_SAFE_ESCAPE_PATTERN = /[\/"\\\x0-\x1f\u2028-\u2029]/
49
49
 
50
50
  def self.native_type?(value) # :nodoc:
51
- (false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
51
+ (false == value || true == value || nil == value || String === value || Symbol === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
52
52
  end
53
53
 
54
54
  def self.native_key?(key) # :nodoc:
@@ -517,11 +517,11 @@ module JSON
517
517
 
518
518
  if empty?
519
519
  state.depth -= 1
520
- return '{}'
520
+ return +'{}'
521
521
  end
522
522
 
523
523
  delim = ",#{state.object_nl}"
524
- result = +"{#{state.object_nl}"
524
+ result = "{#{state.object_nl}"
525
525
  first = true
526
526
  key_type = nil
527
527
  indent = !state.object_nl.empty?
@@ -558,7 +558,7 @@ module JSON
558
558
  raise TypeError, "#{key.class}#to_s returns an instance of #{key_str.class}, expected a String"
559
559
  end
560
560
 
561
- result = +"#{result}#{key_json}#{state.space_before}:#{state.space}"
561
+ result = "#{result}#{key_json}#{state.space_before}:#{state.space}"
562
562
  if state.strict? && !Generator.native_type?(value)
563
563
  if state.as_json
564
564
  value = state.as_json.call(value, false)
@@ -609,7 +609,7 @@ module JSON
609
609
 
610
610
  if empty?
611
611
  state.depth -= 1
612
- return '[]'
612
+ return +'[]'
613
613
  end
614
614
 
615
615
  result = '['.dup
@@ -734,17 +734,17 @@ module JSON
734
734
 
735
735
  module TrueClass
736
736
  # Returns a JSON string for true: 'true'.
737
- def to_json(*) 'true' end
737
+ def to_json(*) +'true' end
738
738
  end
739
739
 
740
740
  module FalseClass
741
741
  # Returns a JSON string for false: 'false'.
742
- def to_json(*) 'false' end
742
+ def to_json(*) +'false' end
743
743
  end
744
744
 
745
745
  module NilClass
746
746
  # Returns a JSON string for nil: 'null'.
747
- def to_json(*) 'null' end
747
+ def to_json(*) +'null' end
748
748
  end
749
749
  end
750
750
  end
data/lib/json/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSON
4
- VERSION = '2.19.2'
4
+ VERSION = '2.19.4'
5
5
  end
data/lib/json.rb CHANGED
@@ -335,8 +335,8 @@ require 'json/common'
335
335
  # JSON.generate(JSON::MinusInfinity)
336
336
  #
337
337
  # Allow:
338
- # ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity]
339
- # JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]'
338
+ # ruby = [Float::NAN, Float::INFINITY, JSON::NaN, JSON::Infinity, JSON::MinusInfinity]
339
+ # JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,NaN,Infinity,-Infinity]'
340
340
  #
341
341
  # ---
342
342
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.19.2
4
+ version: 2.19.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -84,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
84
  - !ruby/object:Gem::Version
85
85
  version: '0'
86
86
  requirements: []
87
- rubygems_version: 4.0.3
87
+ rubygems_version: 4.0.6
88
88
  specification_version: 4
89
89
  summary: JSON Implementation for Ruby
90
90
  test_files: []