json
Advanced tools
+13
-1
@@ -5,2 +5,6 @@ # Changes | ||
| ### 2026-04-19 (2.19.4) | ||
| * Fix parsing of out of range floats (very large exponents that lead ot either `0.0` or `Inf`). | ||
| ### 2026-03-25 (2.19.3) | ||
@@ -12,3 +16,3 @@ | ||
| * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. | ||
| * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`. | ||
@@ -33,2 +37,6 @@ ### 2026-03-08 (2.19.1) | ||
| ### 2026-03-18 (2.17.1.2) - Security Backport | ||
| * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`. | ||
| ### 2025-12-04 (2.17.1) | ||
@@ -60,2 +68,6 @@ | ||
| ### 2026-03-18 (2.15.2.1) - Security Backport | ||
| * Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`. | ||
| ### 2025-10-25 (2.15.2) | ||
@@ -62,0 +74,0 @@ |
@@ -8,2 +8,4 @@ require 'mkmf' | ||
| append_cflags("-std=c99") | ||
| have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 | ||
| $defs << "-DJSON_GENERATOR" | ||
@@ -10,0 +12,0 @@ $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0" |
+11
-0
@@ -57,2 +57,13 @@ #ifndef _JSON_H_ | ||
| #ifdef RUBY_TYPED_EMBEDDABLE | ||
| # define HAVE_RUBY_TYPED_EMBEDDABLE 1 | ||
| #else | ||
| # ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE | ||
| # define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE | ||
| # define HAVE_RUBY_TYPED_EMBEDDABLE 1 | ||
| # else | ||
| # define RUBY_TYPED_EMBEDDABLE 0 | ||
| # endif | ||
| #endif | ||
| #ifndef NORETURN | ||
@@ -59,0 +70,0 @@ #if defined(__has_attribute) && __has_attribute(noreturn) |
@@ -10,2 +10,6 @@ # frozen_string_literal: true | ||
| if RUBY_ENGINE == "ruby" | ||
| have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 | ||
| end | ||
| append_cflags("-std=c99") | ||
@@ -12,0 +16,0 @@ |
@@ -244,7 +244,15 @@ #include "../json.h" | ||
| long index; | ||
| for (index = 0; index < stack->head; index++) { | ||
| rb_gc_mark(stack->ptr[index]); | ||
| if (stack && stack->ptr) { | ||
| for (index = 0; index < stack->head; index++) { | ||
| rb_gc_mark(stack->ptr[index]); | ||
| } | ||
| } | ||
| } | ||
| static void rvalue_stack_free_buffer(rvalue_stack *stack) | ||
| { | ||
| ruby_xfree(stack->ptr); | ||
| stack->ptr = NULL; | ||
| } | ||
| static void rvalue_stack_free(void *ptr) | ||
@@ -254,4 +262,6 @@ { | ||
| if (stack) { | ||
| ruby_xfree(stack->ptr); | ||
| rvalue_stack_free_buffer(stack); | ||
| #ifndef HAVE_RUBY_TYPED_EMBEDDABLE | ||
| ruby_xfree(stack); | ||
| #endif | ||
| } | ||
@@ -267,4 +277,4 @@ } | ||
| static const rb_data_type_t JSON_Parser_rvalue_stack_type = { | ||
| "JSON::Ext::Parser/rvalue_stack", | ||
| { | ||
| .wrap_struct_name = "JSON::Ext::Parser/rvalue_stack", | ||
| .function = { | ||
| .dmark = rvalue_stack_mark, | ||
@@ -274,4 +284,3 @@ .dfree = rvalue_stack_free, | ||
| }, | ||
| 0, 0, | ||
| RUBY_TYPED_FREE_IMMEDIATELY, | ||
| .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE, | ||
| }; | ||
@@ -298,4 +307,8 @@ | ||
| TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack); | ||
| #ifdef HAVE_RUBY_TYPED_EMBEDDABLE | ||
| rvalue_stack_free_buffer(stack); | ||
| #else | ||
| rvalue_stack_free(stack); | ||
| RTYPEDDATA_DATA(handle) = NULL; | ||
| rvalue_stack_free(stack); | ||
| #endif | ||
| } | ||
@@ -351,3 +364,3 @@ } | ||
| typedef struct JSON_ParserStateStruct { | ||
| VALUE stack_handle; | ||
| VALUE *stack_handle; | ||
| const char *start; | ||
@@ -445,5 +458,4 @@ const char *cursor; | ||
| VALUE msg = rb_sprintf(format, ptr); | ||
| VALUE message = rb_enc_sprintf(enc_utf8, "%s at line %ld column %ld", RSTRING_PTR(msg), line, column); | ||
| RB_GC_GUARD(msg); | ||
| VALUE message = rb_enc_sprintf(enc_utf8, format, ptr); | ||
| rb_str_catf(message, " at line %ld column %ld", line, column); | ||
| return message; | ||
@@ -853,3 +865,3 @@ } | ||
| */ | ||
| static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int32_t exponent, bool negative, | ||
| static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative, | ||
| const char *start, const char *end) | ||
@@ -862,2 +874,10 @@ { | ||
| if (RB_UNLIKELY(exponent > INT32_MAX)) { | ||
| return negative ? CMinusInfinity : CInfinity; | ||
| } | ||
| if (RB_UNLIKELY(exponent < INT32_MIN)) { | ||
| return rb_float_new(negative ? -0.0 : 0.0); | ||
| } | ||
| // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case) | ||
@@ -869,3 +889,3 @@ // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308) | ||
| return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, exponent, negative)); | ||
| return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, (int32_t)exponent, negative)); | ||
| } | ||
@@ -924,5 +944,2 @@ | ||
| rb_exc_raise(parse_error_new(message, line, column)); | ||
| raise_parse_error(RSTRING_PTR(message), state); | ||
| RB_GC_GUARD(message); | ||
| } | ||
@@ -964,3 +981,3 @@ | ||
| } | ||
| rvalue_stack_push(state->stack, value, &state->stack_handle, &state->stack); | ||
| rvalue_stack_push(state->stack, value, state->stack_handle, &state->stack); | ||
| return value; | ||
@@ -1150,3 +1167,3 @@ } | ||
| // Variables for Ryu optimization - extract digits during parsing | ||
| int32_t exponent = 0; | ||
| int64_t exponent = 0; | ||
| int decimal_point_pos = -1; | ||
@@ -1195,3 +1212,3 @@ uint64_t mantissa = 0; | ||
| exponent = negative_exponent ? -((int32_t)abs_exponent) : ((int32_t)abs_exponent); | ||
| exponent = negative_exponent ? -abs_exponent : abs_exponent; | ||
| } | ||
@@ -1577,2 +1594,3 @@ | ||
| VALUE stack_handle = 0; | ||
| JSON_ParserState _state = { | ||
@@ -1583,2 +1601,3 @@ .start = start, | ||
| .stack = &stack, | ||
| .stack_handle = &stack_handle, | ||
| }; | ||
@@ -1591,4 +1610,4 @@ JSON_ParserState *state = &_state; | ||
| // it won't cause a leak. | ||
| rvalue_stack_eagerly_release(state->stack_handle); | ||
| rvalue_stack_eagerly_release(stack_handle); | ||
| RB_GC_GUARD(stack_handle); | ||
| json_ensure_eof(state); | ||
@@ -1631,8 +1650,2 @@ | ||
| static void JSON_ParserConfig_free(void *ptr) | ||
| { | ||
| JSON_ParserConfig *config = ptr; | ||
| ruby_xfree(config); | ||
| } | ||
| static size_t JSON_ParserConfig_memsize(const void *ptr) | ||
@@ -1644,10 +1657,9 @@ { | ||
| static const rb_data_type_t JSON_ParserConfig_type = { | ||
| "JSON::Ext::Parser/ParserConfig", | ||
| { | ||
| .wrap_struct_name = "JSON::Ext::Parser/ParserConfig", | ||
| .function = { | ||
| JSON_ParserConfig_mark, | ||
| JSON_ParserConfig_free, | ||
| RUBY_DEFAULT_FREE, | ||
| JSON_ParserConfig_memsize, | ||
| }, | ||
| 0, 0, | ||
| RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE, | ||
| .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, | ||
| }; | ||
@@ -1654,0 +1666,0 @@ |
+2
-2
@@ -338,4 +338,4 @@ # frozen_string_literal: true | ||
| # Allow: | ||
| # ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity] | ||
| # JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]' | ||
| # ruby = [Float::NAN, Float::INFINITY, JSON::NaN, JSON::Infinity, JSON::MinusInfinity] | ||
| # JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,NaN,Infinity,-Infinity]' | ||
| # | ||
@@ -342,0 +342,0 @@ # --- |
@@ -51,3 +51,3 @@ # frozen_string_literal: true | ||
| def self.native_type?(value) # :nodoc: | ||
| (false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) | ||
| (false == value || true == value || nil == value || String === value || Symbol === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) | ||
| end | ||
@@ -521,7 +521,7 @@ | ||
| state.depth -= 1 | ||
| return '{}' | ||
| return +'{}' | ||
| end | ||
| delim = ",#{state.object_nl}" | ||
| result = +"{#{state.object_nl}" | ||
| result = "{#{state.object_nl}" | ||
| first = true | ||
@@ -563,3 +563,3 @@ key_type = nil | ||
| result = +"#{result}#{key_json}#{state.space_before}:#{state.space}" | ||
| result = "#{result}#{key_json}#{state.space_before}:#{state.space}" | ||
| if state.strict? && !Generator.native_type?(value) | ||
@@ -615,3 +615,3 @@ if state.as_json | ||
| state.depth -= 1 | ||
| return '[]' | ||
| return +'[]' | ||
| end | ||
@@ -741,3 +741,3 @@ | ||
| # Returns a JSON string for true: 'true'. | ||
| def to_json(*) 'true' end | ||
| def to_json(*) +'true' end | ||
| end | ||
@@ -747,3 +747,3 @@ | ||
| # Returns a JSON string for false: 'false'. | ||
| def to_json(*) 'false' end | ||
| def to_json(*) +'false' end | ||
| end | ||
@@ -753,3 +753,3 @@ | ||
| # Returns a JSON string for nil: 'null'. | ||
| def to_json(*) 'null' end | ||
| def to_json(*) +'null' end | ||
| end | ||
@@ -756,0 +756,0 @@ end |
| # frozen_string_literal: true | ||
| module JSON | ||
| VERSION = '2.19.3' | ||
| VERSION = '2.19.4' | ||
| end |
Sorry, the diff of this file is too big to display