json 2.19.3 → 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: 3182d9103a2ee3b673b923a4789e947a9cddb850f870f7fdb54d93f3bd1a5493
4
- data.tar.gz: 0ee85345c9e1c99223f9cc3e859e4a7295f40f88461ca0f47059cfcc4e80154c
3
+ metadata.gz: 48a111e63867f6bb2c0cb1b2740a898f3102bc07ac196136e26ca9b68f510f89
4
+ data.tar.gz: 2f760d8afae80555c483afaaa8bead26f045d26c394cc07843a85deaff7fdd40
5
5
  SHA512:
6
- metadata.gz: dedf64066bc4017b977330468b01613ce1d4a78c267b38519b7ef2705368e5d73187b64ca99777afaf900cc5f58523a722833e83c0ee011944bf15b34c33479e
7
- data.tar.gz: c187fa755e0a7bd2e2ddb74498062a8138ac4882630713e3372886b5648a22a453b1a388174038b2d4144a724e1d44d5b444b190e49e3e7eb2047d8b98eef0a8
6
+ metadata.gz: 6387b2c483c8f4d7ed304f33a3553a2fde78c04efeff7ae32a1e952380df92d6df87b543c22832907b007f758f09bd2f80ef4db0b5753e46fdf73f0e264e9e02
7
+ data.tar.gz: c3c855b9e460768f83516914580c10854ea2b28ee73976e311ce55a0d2265576f65a23c41cccc47c7443d79883658962d1cf923880685d555b3af1ba0ebcb056
data/CHANGES.md CHANGED
@@ -2,13 +2,17 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2026-04-19 (2.19.4)
6
+
7
+ * Fix parsing of out of range floats (very large exponents that lead ot either `0.0` or `Inf`).
8
+
5
9
  ### 2026-03-25 (2.19.3)
6
10
 
7
11
  * Fix handling of unescaped control characters preceeded by a backslash.
8
12
 
9
13
  ### 2026-03-18 (2.19.2)
10
14
 
11
- * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`.
15
+ * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`.
12
16
 
13
17
  ### 2026-03-08 (2.19.1)
14
18
 
@@ -28,6 +32,10 @@
28
32
 
29
33
  * Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines).
30
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
+
31
39
  ### 2025-12-04 (2.17.1)
32
40
 
33
41
  * Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned.
@@ -54,6 +62,10 @@
54
62
  * Optimized numbers parsing using SWAR (thanks to Scott Myron).
55
63
  * Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron).
56
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
+
57
69
  ### 2025-10-25 (2.15.2)
58
70
 
59
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
 
@@ -843,7 +855,7 @@ NOINLINE(static) VALUE json_decode_large_float(const char *start, long len)
843
855
  /* Ruby JSON optimized float decoder using vendored Ryu algorithm
844
856
  * Accepts pre-extracted mantissa and exponent from first-pass validation
845
857
  */
846
- 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,
847
859
  const char *start, const char *end)
848
860
  {
849
861
  if (RB_UNLIKELY(config->decimal_class)) {
@@ -851,13 +863,21 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis
851
863
  return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
852
864
  }
853
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
+
854
874
  // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case)
855
875
  // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308)
856
876
  if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) {
857
877
  return json_decode_large_float(start, end - start);
858
878
  }
859
879
 
860
- 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));
861
881
  }
862
882
 
863
883
  static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count)
@@ -911,9 +931,6 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
911
931
  cursor_position(state, &line, &column);
912
932
  rb_str_concat(message, build_parse_error_message("", state, line, column)) ;
913
933
  rb_exc_raise(parse_error_new(message, line, column));
914
-
915
- raise_parse_error(RSTRING_PTR(message), state);
916
- RB_GC_GUARD(message);
917
934
  }
918
935
 
919
936
  static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
@@ -950,7 +967,7 @@ static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *
950
967
  if (RB_UNLIKELY(config->on_load_proc)) {
951
968
  value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil);
952
969
  }
953
- rvalue_stack_push(state->stack, value, &state->stack_handle, &state->stack);
970
+ rvalue_stack_push(state->stack, value, state->stack_handle, &state->stack);
954
971
  return value;
955
972
  }
956
973
 
@@ -1135,7 +1152,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1135
1152
  const char first_digit = *state->cursor;
1136
1153
 
1137
1154
  // Variables for Ryu optimization - extract digits during parsing
1138
- int32_t exponent = 0;
1155
+ int64_t exponent = 0;
1139
1156
  int decimal_point_pos = -1;
1140
1157
  uint64_t mantissa = 0;
1141
1158
 
@@ -1179,7 +1196,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1179
1196
  raise_parse_error_at("invalid number: %s", state, start);
1180
1197
  }
1181
1198
 
1182
- exponent = negative_exponent ? -((int32_t)abs_exponent) : ((int32_t)abs_exponent);
1199
+ exponent = negative_exponent ? -abs_exponent : abs_exponent;
1183
1200
  }
1184
1201
 
1185
1202
  if (integer) {
@@ -1560,11 +1577,13 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1560
1577
  const char *start;
1561
1578
  RSTRING_GETMEM(Vsource, start, len);
1562
1579
 
1580
+ VALUE stack_handle = 0;
1563
1581
  JSON_ParserState _state = {
1564
1582
  .start = start,
1565
1583
  .cursor = start,
1566
1584
  .end = start + len,
1567
1585
  .stack = &stack,
1586
+ .stack_handle = &stack_handle,
1568
1587
  };
1569
1588
  JSON_ParserState *state = &_state;
1570
1589
 
@@ -1572,8 +1591,8 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1572
1591
 
1573
1592
  // This may be skipped in case of exception, but
1574
1593
  // it won't cause a leak.
1575
- rvalue_stack_eagerly_release(state->stack_handle);
1576
-
1594
+ rvalue_stack_eagerly_release(stack_handle);
1595
+ RB_GC_GUARD(stack_handle);
1577
1596
  json_ensure_eof(state);
1578
1597
 
1579
1598
  return result;
@@ -1611,26 +1630,19 @@ static void JSON_ParserConfig_mark(void *ptr)
1611
1630
  rb_gc_mark(config->decimal_class);
1612
1631
  }
1613
1632
 
1614
- static void JSON_ParserConfig_free(void *ptr)
1615
- {
1616
- JSON_ParserConfig *config = ptr;
1617
- ruby_xfree(config);
1618
- }
1619
-
1620
1633
  static size_t JSON_ParserConfig_memsize(const void *ptr)
1621
1634
  {
1622
1635
  return sizeof(JSON_ParserConfig);
1623
1636
  }
1624
1637
 
1625
1638
  static const rb_data_type_t JSON_ParserConfig_type = {
1626
- "JSON::Ext::Parser/ParserConfig",
1627
- {
1639
+ .wrap_struct_name = "JSON::Ext::Parser/ParserConfig",
1640
+ .function = {
1628
1641
  JSON_ParserConfig_mark,
1629
- JSON_ParserConfig_free,
1642
+ RUBY_DEFAULT_FREE,
1630
1643
  JSON_ParserConfig_memsize,
1631
1644
  },
1632
- 0, 0,
1633
- 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,
1634
1646
  };
1635
1647
 
1636
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.3'
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.3
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: []