-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't call rb_str_set_len
while released the GVL.
#88
base: master
Are you sure you want to change the base?
Conversation
99c6c62
to
07652b7
Compare
ce5523c
to
977580d
Compare
@jeremyevans I'm looking at failure cases. Nothing about: static VALUE
rb_inflate_inflate(int argc, VALUE* argv, VALUE obj)
{
struct zstream *z = get_zstream(obj);
VALUE dst, src, opts, buffer = Qnil;
if (OPTHASH_GIVEN_P(opts)) {
VALUE buf;
rb_get_kwargs(opts, &id_buffer, 0, 1, &buf);
if (buf != Qundef && buf != Qnil) {
buffer = StringValue(buf);
}
}
if (buffer != Qnil) {
if (!(ZSTREAM_REUSE_BUFFER_P(z) && z->buf == buffer)) {
long len = RSTRING_LEN(buffer);
if (len >= ZSTREAM_AVAIL_OUT_STEP_MAX) {
rb_str_modify(buffer);
}
else {
len = ZSTREAM_AVAIL_OUT_STEP_MAX - len;
rb_str_modify_expand(buffer, len);
}
rb_str_set_len(buffer, 0);
z->flags |= ZSTREAM_REUSE_BUFFER;
z->buf = buffer;
}
} else if (ZSTREAM_REUSE_BUFFER_P(z)) {
z->flags &= ~ZSTREAM_REUSE_BUFFER;
z->buf = Qnil;
}
rb_scan_args(argc, argv, "10", &src);
if (ZSTREAM_IS_FINISHED(z)) {
if (NIL_P(src)) {
dst = zstream_detach_buffer(z);
}
else {
StringValue(src);
zstream_append_buffer2(z, src);
if (ZSTREAM_REUSE_BUFFER_P(z)) {
dst = rb_str_resize(buffer, 0);
} else {
dst = rb_str_new(0, 0);
}
}
}
else {
do_inflate(z, src);
dst = zstream_detach_buffer(z);
if (ZSTREAM_IS_FINISHED(z)) {
zstream_passthrough_input(z);
}
}
return dst;
} ...looks safe to me in a multi-threaded context. For example, what if one call assigns to |
2af7a84
to
2fe600e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. Please give some time for @nobu to review as well.
static VALUE | ||
rb_inflate_inflate(int argc, VALUE* argv, VALUE obj) | ||
rb_inflate_inflate_body(VALUE _arguments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would be to only prefix with underscore if the argument is not used at all. But I'll leave that up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to leave it as I've done it. I understand your position. For me, _arguments
is immediately converted to a known type. _arguments
should not be used in the function except for this one conversion.
I found a (rather contrived) example that causes Ruby to segfault: #!/usr/bin/env ruby
require 'zlib'
require 'securerandom'
class String
def each_slice(n)
0.step(self.size, n) do |i|
yield self[i, n]
end
end
end
original_data = SecureRandom.random_bytes(10_000_000)
compressed_data = Zlib.deflate(original_data)
buffer = String.new(capacity: 10_000_000)
thread = Thread.new do
while true
buffer.clear
end
end
$stderr.puts "Entering decompression loop..."
1.times do
decompressor = Zlib::Inflate.new
decompressed_data = String.new
compressed_data.each_slice(1_000_000) do |chunk|
decompressed_data += decompressor.inflate(chunk, buffer: buffer)
# ./test.rb:32: [BUG] probable buffer overflow: 16384 for 15
end
decompressed_data += decompressor.finish
if decompressed_data != original_data
raise "Data corruption: #{decompressed_data.inspect}"
end
end
$stderr.puts "Decompression loop completed."
thread.kill Basically, the internal buffer should probably use
|
After adding
|
3d2f241
to
9966ce5
Compare
9966ce5
to
938a34d
Compare
- Several string manipulation methods were invoked while the GVL was released. This is unsafe. - The mutex protecting multi-threaded access was not covering buffer state manipulation, leading to data corruption and out-of-bounds writes. - Using `rb_str_locktmp` prevents changes to buffer while it's in use. [Bug #20863]
938a34d
to
2e169ab
Compare
https://bugs.ruby-lang.org/issues/20863