json 2.19.6 → 2.19.8

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: d2f786d8fccfe7906f9ec68f2a8f1cb9b4fb458029b46e3bba7904a6e22e443f
4
- data.tar.gz: 3fe8d97828cc573a98f4a0ccd88c2dc1fb008437dc115a1cd4d631b7a16f9df1
3
+ metadata.gz: 5f07e2772537eccf97069c6a7d7290aceba9a18c6b635e8e822bc7b92137c678
4
+ data.tar.gz: cbb25a9d0e3434eba5d9fa2be9af5a69e1fe062c5113c461d854d129122adeed
5
5
  SHA512:
6
- metadata.gz: 6a39aec470a7ec7da944a8361ea219352f1216b3a4c4b2de7dd35fc87d4c791286f63b7744349ca4da607c37e53b1fecf450e62ef6cd02d0147eba0fb1edf5c2
7
- data.tar.gz: 8cd952ae28ad7bc52f92ce49cd5cc6411835e3801a2c5cfc4a4b6d503a4bdb3dea3d726a4c4b2f7a75f0d3ec68f2d948bec52588cda37669c7d80ced0d527bb8
6
+ metadata.gz: 2783e5483b100728ae73451008d3d5f880cc6e9dc663d773ed9dcdf27c23c3523c345f7ca70ae0e3687ab3b11262b380a7ecf455906d72b996f07eedbb1b14f8
7
+ data.tar.gz: 84ab5ff3feb2d961b4bd5d759d444b006e8ccaa6802465203ed04a8718db43b4473859fdd613c106b0cc53d4e96d1da8b4a16550361f455964a4a1a75ae3cb23
data/CHANGES.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ ### 2026-06-03 (2.19.8)
6
+
7
+ * Fix 1-byte buffer overread on EOS errors.
8
+ * Handle invalid types passed as `max_nesting` option.
9
+
10
+ ### 2026-05-28 (2.19.7)
11
+
12
+ * Fix some more edge cases with out of range floats.
13
+ * Ensure the string provided to `JSON.parse` can't be mutated during parsing.
14
+ * Add missing write barriers in `State#dup`.
15
+ * Further validate generator `depth` config.
16
+
5
17
  ### 2026-05-28 (2.19.6)
6
18
 
7
19
  * Cleanly handle overly large `depth` generator argument.
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
@@ -1367,12 +1367,14 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
1367
1367
  if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State");
1368
1368
 
1369
1369
  MEMCPY(objState, origState, JSON_Generator_State, 1);
1370
- objState->indent = origState->indent;
1371
- objState->space = origState->space;
1372
- objState->space_before = origState->space_before;
1373
- objState->object_nl = origState->object_nl;
1374
- objState->array_nl = origState->array_nl;
1375
- objState->as_json = origState->as_json;
1370
+
1371
+ RB_OBJ_WRITTEN(obj, Qundef, objState->indent);
1372
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space);
1373
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space_before);
1374
+ RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl);
1375
+ RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl);
1376
+ RB_OBJ_WRITTEN(obj, Qundef, objState->as_json);
1377
+
1376
1378
  return obj;
1377
1379
  }
1378
1380
 
@@ -1579,7 +1581,7 @@ static VALUE cState_max_nesting(VALUE self)
1579
1581
 
1580
1582
  static long long_config(VALUE num)
1581
1583
  {
1582
- return RTEST(num) ? FIX2LONG(num) : 0;
1584
+ return RTEST(num) ? NUM2LONG(num) : 0;
1583
1585
  }
1584
1586
 
1585
1587
  // depth must never be negative; reject early with a clear error.
@@ -1590,6 +1592,9 @@ static long depth_config(VALUE num)
1590
1592
  if (RB_UNLIKELY(d < 0)) {
1591
1593
  rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d);
1592
1594
  }
1595
+ if (RB_UNLIKELY(d > INT_MAX)) {
1596
+ rb_raise(rb_eArgError, "depth is too large (got %ld)", d);
1597
+ }
1593
1598
  return d;
1594
1599
  }
1595
1600
 
@@ -385,6 +385,13 @@ static inline char peek(JSON_ParserState *state)
385
385
 
386
386
  static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out)
387
387
  {
388
+ JSON_ASSERT(state->cursor <= state->end);
389
+
390
+ // Redundant but helpful for hardening
391
+ if (RB_UNLIKELY(state->cursor > state->end)) {
392
+ state->cursor = state->end;
393
+ }
394
+
388
395
  const char *cursor = state->cursor;
389
396
  long column = 0;
390
397
  long line = 1;
@@ -1022,6 +1029,13 @@ ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state)
1022
1029
  }
1023
1030
  state->cursor++;
1024
1031
  }
1032
+
1033
+ // If the string ended with an unterminated escape sequence, we might
1034
+ // have gone past the end.
1035
+ if (RB_UNLIKELY(state->cursor > state->end)) {
1036
+ state->cursor = state->end;
1037
+ }
1038
+
1025
1039
  return false;
1026
1040
  }
1027
1041
 
@@ -1202,7 +1216,11 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
1202
1216
  raise_parse_error_at("invalid number: %s", state, start);
1203
1217
  }
1204
1218
 
1205
- exponent = negative_exponent ? -abs_exponent : abs_exponent;
1219
+ if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) {
1220
+ exponent = negative_exponent ? INT64_MIN : INT64_MAX;
1221
+ } else {
1222
+ exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent;
1223
+ }
1206
1224
  }
1207
1225
 
1208
1226
  if (integer) {
@@ -1457,18 +1475,21 @@ static void json_ensure_eof(JSON_ParserState *state)
1457
1475
 
1458
1476
  static VALUE convert_encoding(VALUE source)
1459
1477
  {
1460
- int encindex = RB_ENCODING_GET(source);
1478
+ StringValue(source);
1479
+ int encindex = RB_ENCODING_GET(source);
1461
1480
 
1462
- if (RB_LIKELY(encindex == utf8_encindex)) {
1463
- return source;
1464
- }
1481
+ if (RB_LIKELY(encindex == utf8_encindex)) {
1482
+ return source;
1483
+ }
1465
1484
 
1466
- if (encindex == binary_encindex) {
1467
- // For historical reason, we silently reinterpret binary strings as UTF-8
1468
- return rb_enc_associate_index(rb_str_dup(source), utf8_encindex);
1469
- }
1485
+ if (encindex == binary_encindex) {
1486
+ // For historical reason, we silently reinterpret binary strings as UTF-8
1487
+ return rb_enc_associate_index(rb_str_dup(source), utf8_encindex);
1488
+ }
1470
1489
 
1471
- return rb_funcall(source, i_encode, 1, Encoding_UTF_8);
1490
+ source = rb_funcall(source, i_encode, 1, Encoding_UTF_8);
1491
+ StringValue(source);
1492
+ return source;
1472
1493
  }
1473
1494
 
1474
1495
  struct parser_config_init_args {
@@ -1583,10 +1604,16 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
1583
1604
  return self;
1584
1605
  }
1585
1606
 
1586
- static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1607
+ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
1587
1608
  {
1588
- Vsource = convert_encoding(StringValue(Vsource));
1589
- StringValue(Vsource);
1609
+ VALUE Vsource = convert_encoding(src);
1610
+
1611
+ // Ensure the string isn't mutated under us.
1612
+ // The classic API to use is `rb_str_locktmp`, but then we'd
1613
+ // need to use `rb_protect` to make sure we always unlock.
1614
+ if (Vsource == src) {
1615
+ Vsource = rb_str_new_frozen(Vsource);
1616
+ }
1590
1617
 
1591
1618
  VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
1592
1619
  rvalue_stack stack = {
@@ -1597,6 +1624,7 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1597
1624
 
1598
1625
  long len;
1599
1626
  const char *start;
1627
+
1600
1628
  RSTRING_GETMEM(Vsource, start, len);
1601
1629
 
1602
1630
  VALUE stack_handle = 0;
@@ -1615,6 +1643,7 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
1615
1643
  // it won't cause a leak.
1616
1644
  rvalue_stack_eagerly_release(stack_handle);
1617
1645
  RB_GC_GUARD(stack_handle);
1646
+ RB_GC_GUARD(Vsource);
1618
1647
  json_ensure_eof(state);
1619
1648
 
1620
1649
  return result;
@@ -1635,9 +1664,6 @@ static VALUE cParserConfig_parse(VALUE self, VALUE Vsource)
1635
1664
 
1636
1665
  static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts)
1637
1666
  {
1638
- Vsource = convert_encoding(StringValue(Vsource));
1639
- StringValue(Vsource);
1640
-
1641
1667
  JSON_ParserConfig _config = {0};
1642
1668
  JSON_ParserConfig *config = &_config;
1643
1669
  parser_config_init(config, opts, false);
@@ -307,6 +307,9 @@ module JSON
307
307
  if !opts.key?(:max_nesting) # defaults to 100
308
308
  @max_nesting = 100
309
309
  elsif opts[:max_nesting]
310
+ unless opts[:max_nesting].is_a?(Integer)
311
+ raise TypeError, ":max_nesting must be an Integer, got: #{opts[:max_nesting].class}"
312
+ end
310
313
  @max_nesting = opts[:max_nesting]
311
314
  else
312
315
  @max_nesting = 0
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.6'
4
+ VERSION = '2.19.8'
5
5
  end
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.6
4
+ version: 2.19.8
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.10
87
+ rubygems_version: 4.0.12
88
88
  specification_version: 4
89
89
  summary: JSON Implementation for Ruby
90
90
  test_files: []