mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
[ruby/json] Prevent use-after-free when strings change during IO generation
https://github.com/ruby/json/commit/f720614728
This commit is contained in:
1 parent
f452e5403e
commit
ad5851e797
3 files changed
+51
-2
No files matched your search
@@ -169,6 +169,10 @@ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr)
|
||||
|
||||
static void fbuffer_append_str(FBuffer *fb, VALUE str)
|
||||
{
|
||||
if (RB_UNLIKELY(fb->io)) {
|
||||
// Growing the buffer may flush it and invoke arbitrary Ruby code.
|
||||
str = rb_str_new_frozen(str);
|
||||
}
|
||||
const char *ptr;
|
||||
size_t len;
|
||||
RSTRING_GETMEM(str, ptr, len);
|
||||
|
||||
@@ -892,12 +892,17 @@ ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *dat
|
||||
|
||||
static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
||||
{
|
||||
VALUE str = obj;
|
||||
if (RB_UNLIKELY(buffer->io)) {
|
||||
// IO writes can mutate the original string while we are reading it.
|
||||
str = rb_str_new_frozen(str);
|
||||
}
|
||||
fbuffer_append_char(buffer, '"');
|
||||
|
||||
long len;
|
||||
search_state search;
|
||||
search.buffer = buffer;
|
||||
RSTRING_GETMEM(obj, search.ptr, len);
|
||||
RSTRING_GETMEM(str, search.ptr, len);
|
||||
search.cursor = search.ptr;
|
||||
search.end = search.ptr + len;
|
||||
|
||||
@@ -908,7 +913,7 @@ static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data
|
||||
search.chunk_end = NULL;
|
||||
#endif /* HAVE_SIMD */
|
||||
|
||||
switch (json_str_coderange(obj)) {
|
||||
switch (json_str_coderange(str)) {
|
||||
case ENC_CODERANGE_7BIT:
|
||||
case ENC_CODERANGE_VALID:
|
||||
if (RB_UNLIKELY(data->state->ascii_only)) {
|
||||
@@ -924,6 +929,7 @@ static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data
|
||||
break;
|
||||
}
|
||||
fbuffer_append_char(buffer, '"');
|
||||
RB_GC_GUARD(str);
|
||||
}
|
||||
|
||||
static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
||||
|
||||
@@ -91,6 +91,45 @@ class JSONGeneratorTest < Test::Unit::TestCase
|
||||
assert_equal '[1]', io.string
|
||||
end
|
||||
|
||||
def test_dump_string_mutated_during_io_write
|
||||
[{}, {ascii_only: true}, {script_safe: true}, {buffer_initial_length: 1}].each do |options|
|
||||
['a', "a\né/"].each do |pattern|
|
||||
string = pattern * 100_000
|
||||
assert_dump_preserves_string_during_io_write(string, string, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_dump_fragment_mutated_during_io_write
|
||||
string = JSON.generate('a' * 100_000)
|
||||
assert_dump_preserves_string_during_io_write(JSON::Fragment.new(string), string)
|
||||
end
|
||||
|
||||
def test_dump_to_json_result_mutated_during_io_write
|
||||
string = JSON.generate('a' * 100_000)
|
||||
object = Object.new
|
||||
object.define_singleton_method(:to_json) { |*| string }
|
||||
assert_dump_preserves_string_during_io_write(object, string)
|
||||
end
|
||||
|
||||
def assert_dump_preserves_string_during_io_write(object, string, options = {})
|
||||
expected = JSON.dump([object], options)
|
||||
io = StringIO.new
|
||||
mutated = false
|
||||
io.define_singleton_method(:write) do |chunk|
|
||||
unless mutated
|
||||
string.setbyte(0, 'b'.ord)
|
||||
string.replace('changed')
|
||||
mutated = true
|
||||
GC.start
|
||||
end
|
||||
super(chunk)
|
||||
end
|
||||
assert_same io, JSON.dump([object], io, options)
|
||||
assert_equal 'changed', string
|
||||
assert_equal true, expected == io.string, 'IO output must preserve the original string'
|
||||
end
|
||||
|
||||
def test_not_frozen
|
||||
[
|
||||
[[], '[]'],
|
||||
|
||||
Reference in new issue
Block a user