Skip to content

Commit 6848748

Browse files
Watson1978claude
authored andcommitted
Fix SEGV when as_json returns a non-String for an invalid symbol
In strict mode, a Symbol whose name is not UTF-8 compatible was passed to generate_json_string, which handed the as_json result to raw_generate_json_string without checking its type. A non-String result was then read as a String, segfaulting the process. Route strict symbols through the T_STRING case of generate_json_general so they reuse its as_json handling. That re-dispatches the result by type, and its as_json_called flag stops a symbol returned by as_json from recursing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d3c7113 commit 6848748

2 files changed

Lines changed: 48 additions & 14 deletions

File tree

ext/json/ext/generator/generator.c

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,15 +1124,6 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d
11241124
}
11251125
}
11261126

1127-
static inline void generate_json_symbol(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
1128-
{
1129-
if (data->state->strict) {
1130-
generate_json_string(buffer, data, rb_sym2str(obj));
1131-
} else {
1132-
generate_json_fallback(buffer, data, obj);
1133-
}
1134-
}
1135-
11361127
static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
11371128
{
11381129
fbuffer_append(buffer, "null", 4);
@@ -1205,6 +1196,7 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d
12051196
static inline void generate_json_general(FBuffer *buffer, struct generate_json_data *data, VALUE obj, bool fallback)
12061197
{
12071198
bool as_json_called = false;
1199+
VALUE klass;
12081200
start:
12091201
if (obj == Qnil) {
12101202
generate_json_null(buffer, data, obj);
@@ -1218,12 +1210,19 @@ static inline void generate_json_general(FBuffer *buffer, struct generate_json_d
12181210
} else if (RB_FLONUM_P(obj)) {
12191211
generate_json_float(buffer, data, obj);
12201212
} else if (RB_STATIC_SYM_P(obj)) {
1221-
generate_json_symbol(buffer, data, obj);
1213+
if (data->state->strict) {
1214+
obj = rb_sym2str(obj);
1215+
klass = rb_cString;
1216+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
1217+
goto generate_string;
1218+
}
1219+
1220+
generate_json_fallback(buffer, data, obj);
12221221
} else {
12231222
goto general;
12241223
}
12251224
} else {
1226-
VALUE klass = RBASIC_CLASS(obj);
1225+
klass = RBASIC_CLASS(obj);
12271226
switch (RB_BUILTIN_TYPE(obj)) {
12281227
case T_BIGNUM:
12291228
generate_json_bignum(buffer, data, obj);
@@ -1236,7 +1235,18 @@ static inline void generate_json_general(FBuffer *buffer, struct generate_json_d
12361235
if (fallback && klass != rb_cArray) goto general;
12371236
generate_json_array(buffer, data, obj);
12381237
break;
1238+
case T_SYMBOL:
1239+
if (data->state->strict) {
1240+
obj = rb_sym2str(obj);
1241+
klass = rb_cString;
1242+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
1243+
goto generate_string;
1244+
}
1245+
1246+
generate_json_fallback(buffer, data, obj);
1247+
break;
12391248
case T_STRING:
1249+
generate_string:
12401250
if (fallback && klass != rb_cString) goto general;
12411251

12421252
if (RB_LIKELY(valid_json_string_p(obj))) {
@@ -1249,9 +1259,6 @@ static inline void generate_json_general(FBuffer *buffer, struct generate_json_d
12491259
goto start;
12501260
}
12511261
break;
1252-
case T_SYMBOL:
1253-
generate_json_symbol(buffer, data, obj);
1254-
break;
12551262
case T_FLOAT:
12561263
if (fallback && klass != rb_cFloat) goto general;
12571264
generate_json_float(buffer, data, obj);

test/json/json_coder_test.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,33 @@ def test_json_coder_string_invalid_encoding
137137
assert_equal 2, calls
138138
end
139139

140+
def test_json_coder_symbol_invalid_encoding
141+
symbol = "\xFF".b.to_sym
142+
143+
calls = 0
144+
coder = JSON::Coder.new do |object, is_key|
145+
calls += 1
146+
object.bytes
147+
end
148+
149+
assert_equal "[[255]]", coder.dump([symbol])
150+
assert_equal 1, calls
151+
152+
coder = JSON::Coder.new { |object, is_key| nil }
153+
assert_equal "[null]", coder.dump([symbol])
154+
155+
coder = JSON::Coder.new { |object, is_key| Object.new }
156+
assert_raise(JSON::GeneratorError) { coder.dump([symbol]) }
157+
158+
if RUBY_ENGINE == "ruby"
159+
coder = JSON::Coder.new { |object, is_key| symbol }
160+
error = assert_raise JSON::GeneratorError do
161+
coder.dump([symbol])
162+
end
163+
assert_equal "source sequence is illegal/malformed utf-8", error.message
164+
end
165+
end
166+
140167
def test_depth
141168
coder = JSON::Coder.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ", depth: 1)
142169
assert_equal %({\n "foo": 42\n }), coder.dump(foo: 42)

0 commit comments

Comments
 (0)