From e26694b82e789e3cd26005a42c0883f1561f0d58 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 18 Mar 2026 18:34:36 +0100 Subject: [PATCH] Release 2.15.2.1 --- CHANGES.md | 4 ++++ ext/json/ext/parser/parser.c | 26 +++++++++++++++++++------- lib/json/version.rb | 2 +- test/json/json_parser_test.rb | 7 +++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 305515fa..61009258 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ### Unreleased +### 2026-03-18 (2.15.2.1) + +* Fix a format string injection vulnerability in JSON.parse(doc, allow_duplicate_key: false). + ### 2025-10-25 (2.15.2) * Fix `JSON::Coder` to have one dedicated depth counter per invocation. diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index 297031dc..53c7f909 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -428,14 +428,9 @@ static void emit_parse_warning(const char *message, JSON_ParserState *state) #define PARSE_ERROR_FRAGMENT_LEN 32 -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_parse_error(const char *format, JSON_ParserState *state) +static VALUE build_parse_error_message(const char *format, JSON_ParserState *state, long line, long column) { unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3]; - long line, column; - cursor_position(state, &line, &column); const char *ptr = "EOF"; if (state->cursor && state->cursor < state->end) { @@ -470,11 +465,23 @@ static void raise_parse_error(const char *format, JSON_ParserState *state) 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); + return message; +} +static VALUE parse_error_new(VALUE message, long line, long column) +{ VALUE exc = rb_exc_new_str(rb_path2class("JSON::ParserError"), message); rb_ivar_set(exc, rb_intern("@line"), LONG2NUM(line)); rb_ivar_set(exc, rb_intern("@column"), LONG2NUM(column)); - rb_exc_raise(exc); + return exc; +} + +NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state) +{ + long line, column; + cursor_position(state, &line, &column); + VALUE message = build_parse_error_message(format, state, line, column); + rb_exc_raise(parse_error_new(message, line, column)); } #ifdef RBIMPL_ATTR_NORETURN @@ -875,6 +882,11 @@ static void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_k rb_inspect(duplicate_key) ); + long line, column; + cursor_position(state, &line, &column); + rb_str_concat(message, build_parse_error_message("", state, line, column)) ; + rb_exc_raise(parse_error_new(message, line, column)); + raise_parse_error(RSTRING_PTR(message), state); RB_GC_GUARD(message); } diff --git a/lib/json/version.rb b/lib/json/version.rb index dc01ba29..b10443dd 100644 --- a/lib/json/version.rb +++ b/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.15.2' + VERSION = '2.15.2.1' end diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 9d387cb8..7e9572d7 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -372,6 +372,13 @@ def test_parse_duplicate_key end end + def test_parse_duplicate_key_escape + error = assert_raise(ParserError) do + JSON.parse('{"%s%s%s%s":1,"%s%s%s%s":2}', allow_duplicate_key: false) + end + assert_match "%s%s%s%s", error.message + end + def test_some_wrong_inputs assert_raise(ParserError) { parse('[] bla') } assert_raise(ParserError) { parse('[] 1') }