Skip to content

Commit 393b41c

Browse files
committed
Fix a format string injection vulnerability
In `JSON.parse(doc, allow_duplicate_key: false)`.
1 parent dbf6bb1 commit 393b41c

File tree

3 files changed

+28
-4
lines changed

3 files changed

+28
-4
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### Unreleased
44

5+
* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`.
6+
57
### 2026-03-08 (2.19.1)
68

79
* Fix a compiler dependent GC bug introduced in `2.18.0`.

ext/json/ext/parser/parser.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,9 @@ static void emit_parse_warning(const char *message, JSON_ParserState *state)
402402

403403
#define PARSE_ERROR_FRAGMENT_LEN 32
404404

405-
NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state)
405+
static VALUE build_parse_error_message(const char *format, JSON_ParserState *state, long line, long column)
406406
{
407407
unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3];
408-
long line, column;
409-
cursor_position(state, &line, &column);
410408

411409
const char *ptr = "EOF";
412410
if (state->cursor && state->cursor < state->end) {
@@ -441,11 +439,23 @@ NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *st
441439
VALUE msg = rb_sprintf(format, ptr);
442440
VALUE message = rb_enc_sprintf(enc_utf8, "%s at line %ld column %ld", RSTRING_PTR(msg), line, column);
443441
RB_GC_GUARD(msg);
442+
return message;
443+
}
444444

445+
static VALUE parse_error_new(VALUE message, long line, long column)
446+
{
445447
VALUE exc = rb_exc_new_str(rb_path2class("JSON::ParserError"), message);
446448
rb_ivar_set(exc, rb_intern("@line"), LONG2NUM(line));
447449
rb_ivar_set(exc, rb_intern("@column"), LONG2NUM(column));
448-
rb_exc_raise(exc);
450+
return exc;
451+
}
452+
453+
NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state)
454+
{
455+
long line, column;
456+
cursor_position(state, &line, &column);
457+
VALUE message = build_parse_error_message(format, state, line, column);
458+
rb_exc_raise(parse_error_new(message, line, column));
449459
}
450460

451461
NORETURN(static) void raise_parse_error_at(const char *format, JSON_ParserState *state, const char *at)
@@ -895,6 +905,11 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
895905
rb_inspect(duplicate_key)
896906
);
897907

908+
long line, column;
909+
cursor_position(state, &line, &column);
910+
rb_str_concat(message, build_parse_error_message("", state, line, column)) ;
911+
rb_exc_raise(parse_error_new(message, line, column));
912+
898913
raise_parse_error(RSTRING_PTR(message), state);
899914
RB_GC_GUARD(message);
900915
}

test/json/json_parser_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,13 @@ def test_parse_duplicate_key
425425
end
426426
end
427427

428+
def test_parse_duplicate_key_escape
429+
error = assert_raise(ParserError) do
430+
JSON.parse('{"%s%s%s%s":1,"%s%s%s%s":2}', allow_duplicate_key: false)
431+
end
432+
assert_match "%s%s%s%s", error.message
433+
end
434+
428435
def test_some_wrong_inputs
429436
assert_raise(ParserError) { parse('[] bla') }
430437
assert_raise(ParserError) { parse('[] 1') }

0 commit comments

Comments
 (0)