Skip to content

Commit cdcfdb8

Browse files
committed
Better defend against to_s or encode not returning a T_STRING
1 parent 49e8df0 commit cdcfdb8

2 files changed

Lines changed: 48 additions & 9 deletions

File tree

‎ext/json/ext/generator/generator.c‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,12 @@ static void vstate_spill(struct generate_json_data *data)
770770
RB_OBJ_WRITTEN(vstate, Qundef, state->sort_keys);
771771
}
772772

773+
static inline VALUE json_to_s(VALUE obj)
774+
{
775+
VALUE tmp = rb_funcall(obj, i_to_s, 0);
776+
return StringValue(tmp);
777+
}
778+
773779
static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj)
774780
{
775781
if (RB_UNLIKELY(!data->vstate)) {
@@ -865,7 +871,12 @@ NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data,
865871
}
866872
}
867873

868-
return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
874+
str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
875+
Check_Type(str, T_STRING);
876+
if (!valid_json_string_p(str)) {
877+
raise_generator_error(str, "source sequence is illegal/malformed utf-8");
878+
}
879+
return str;
869880
}
870881

871882
ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
@@ -1114,9 +1125,7 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d
11141125
Check_Type(tmp, T_STRING);
11151126
fbuffer_append_str(buffer, tmp);
11161127
} else {
1117-
tmp = rb_funcall(obj, i_to_s, 0);
1118-
Check_Type(tmp, T_STRING);
1119-
generate_json_string(buffer, data, tmp);
1128+
generate_json_string(buffer, data, json_to_s(obj));
11201129
}
11211130
}
11221131

@@ -1157,8 +1166,7 @@ static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *dat
11571166
generate_json_rfc8785_number(buffer, obj);
11581167
return;
11591168
}
1160-
VALUE tmp = rb_funcall(obj, i_to_s, 0);
1161-
fbuffer_append_str(buffer, StringValue(tmp));
1169+
fbuffer_append_str(buffer, json_to_s(obj));
11621170
}
11631171

11641172
static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
@@ -1177,11 +1185,10 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
11771185
return;
11781186
}
11791187
}
1180-
raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", rb_funcall(obj, i_to_s, 0));
1188+
raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", json_to_s(obj));
11811189
}
11821190

1183-
VALUE tmp = rb_funcall(obj, i_to_s, 0);
1184-
fbuffer_append_str(buffer, tmp);
1191+
fbuffer_append_str(buffer, json_to_s(obj));
11851192
return;
11861193
}
11871194

‎test/json/json_generator_test.rb‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,38 @@ def test_frozen
12231223
end
12241224
end
12251225

1226+
def test_adversary_encode
1227+
fake_string = Class.new(String) do
1228+
def to_s
1229+
self
1230+
end
1231+
1232+
def encode(*)
1233+
42
1234+
end
1235+
end
1236+
1237+
assert_raise TypeError, JSON::GeneratorError do
1238+
JSON.generate({fake_string.new("\xff".b) => 1})
1239+
end
1240+
1241+
no_to_json = Class.new do
1242+
undef_method :to_json
1243+
1244+
def initialize(str)
1245+
@str = str
1246+
end
1247+
1248+
def to_s
1249+
@str
1250+
end
1251+
end
1252+
1253+
assert_raise TypeError, JSON::GeneratorError do
1254+
JSON.generate([no_to_json.new(fake_string.new("\xff".b))])
1255+
end
1256+
end
1257+
12261258
# The case when the State is frozen is tested in JSONCoderTest#test_nesting_recovery
12271259
def test_nesting_recovery
12281260
state = JSON::State.new

0 commit comments

Comments
 (0)