Skip to content

Commit 74f916a

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 74f916a

2 files changed

Lines changed: 44 additions & 13 deletions

File tree

ext/json/ext/generator/generator.c

Lines changed: 17 additions & 13 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);
@@ -1218,7 +1209,13 @@ static inline void generate_json_general(FBuffer *buffer, struct generate_json_d
12181209
} else if (RB_FLONUM_P(obj)) {
12191210
generate_json_float(buffer, data, obj);
12201211
} else if (RB_STATIC_SYM_P(obj)) {
1221-
generate_json_symbol(buffer, data, obj);
1212+
if (data->state->strict) {
1213+
obj = rb_sym2str(obj);
1214+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
1215+
goto generate_string;
1216+
}
1217+
1218+
generate_json_fallback(buffer, data, obj);
12221219
} else {
12231220
goto general;
12241221
}
@@ -1236,9 +1233,19 @@ static inline void generate_json_general(FBuffer *buffer, struct generate_json_d
12361233
if (fallback && klass != rb_cArray) goto general;
12371234
generate_json_array(buffer, data, obj);
12381235
break;
1236+
case T_SYMBOL:
1237+
if (data->state->strict) {
1238+
obj = rb_sym2str(obj);
1239+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
1240+
goto generate_string;
1241+
}
1242+
1243+
generate_json_fallback(buffer, data, obj);
1244+
break;
12391245
case T_STRING:
12401246
if (fallback && klass != rb_cString) goto general;
12411247

1248+
generate_string:
12421249
if (RB_LIKELY(valid_json_string_p(obj))) {
12431250
raw_generate_json_string(buffer, data, obj);
12441251
} else if (as_json_called) {
@@ -1249,9 +1256,6 @@ static inline void generate_json_general(FBuffer *buffer, struct generate_json_d
12491256
goto start;
12501257
}
12511258
break;
1252-
case T_SYMBOL:
1253-
generate_json_symbol(buffer, data, obj);
1254-
break;
12551259
case T_FLOAT:
12561260
if (fallback && klass != rb_cFloat) goto general;
12571261
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)