From 83dbd3f615bd8e7a31571124059eb3264f73c559 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 18 Mar 2026 08:56:17 +0100 Subject: [PATCH] Fix a format string injection vulnerability In `JSON.parse(doc, allow_duplicate_key: false)`. --- ext/json/parser/parser.c | 26 +++++++++++++++++++------- test/json/json_parser_test.rb | 7 +++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 8f9729ef28..a05c5e9657 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -400,14 +400,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) { @@ -442,11 +437,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 @@ -896,6 +903,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/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index ec9391909d..61ea35d1f9 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -411,6 +411,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') }