mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
[ruby/zlib] Fix stale next_out in Zlib::Inflate#inflate(buffer:)
When a call with buffer: left the stream in progress (it raised
Zlib::NeedDict, or the block form has more input to come), the next run
reused the next_out saved by the previous call. If the buffer had been
modified in between, it pointed into freed or reallocated memory.
d = Zlib::Deflate.new
d.set_dictionary("dict")
data = d.deflate("A" * 12000, Zlib::FINISH)
inf = Zlib::Inflate.new
buf = String.new(capacity: 1 << 14)
begin; inf.inflate(data, buffer: buf); rescue Zlib::NeedDict; end
buf.clear
inf.set_dictionary("dict")
inf.inflate("", buffer: buf)
#=> [BUG] probable buffer overflow: 12000 for 15
Re-derive next_out/avail_out from the buffer at the start of every run,
and reset them when buffer: is dropped.
Reported by richardjiakangsun (https://hackerone.com/richardjiakangsun).
https://github.com/ruby/zlib/commit/e244392b8d
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
8074d3f475
commit
7d104f9de0
2 files changed
+100
No files matched your search
@@ -1154,6 +1154,12 @@ zstream_run_try(VALUE value_arg)
|
||||
}
|
||||
z->flags |= ZSTREAM_IN_PROGRESS;
|
||||
|
||||
if (ZSTREAM_REUSE_BUFFER_P(z)) {
|
||||
/* always re-derive next_out; the user may have modified z->buf */
|
||||
z->stream.next_out = NULL;
|
||||
z->stream.avail_out = 0;
|
||||
}
|
||||
|
||||
if (NIL_P(z->input) && len == 0) {
|
||||
z->stream.next_in = (Bytef*)"";
|
||||
z->stream.avail_in = 0;
|
||||
@@ -2201,6 +2207,8 @@ rb_inflate_inflate_body(VALUE _arguments)
|
||||
} else if (ZSTREAM_REUSE_BUFFER_P(z)) {
|
||||
z->flags &= ~ZSTREAM_REUSE_BUFFER;
|
||||
z->buf = Qnil;
|
||||
z->stream.next_out = NULL;
|
||||
z->stream.avail_out = 0;
|
||||
}
|
||||
rb_scan_args(argc, argv, "10", &src);
|
||||
|
||||
|
||||
@@ -427,6 +427,98 @@ if defined? Zlib
|
||||
}
|
||||
end
|
||||
|
||||
def test_inflate_buffer_modified_between_calls
|
||||
# In the block form the buffer is kept across calls while the stream
|
||||
# is in progress; the application may modify it in between.
|
||||
src = "0" * 100_000
|
||||
deflated = Zlib::Deflate.deflate(src)
|
||||
half = deflated.bytesize / 2
|
||||
|
||||
# buffer cleared between calls: the output pending in the buffer is
|
||||
# discarded by the application, the rest must still be inflated
|
||||
z = Zlib::Inflate.new
|
||||
buf = String.new(capacity: 1 << 14)
|
||||
inflated = "".dup
|
||||
z.inflate(deflated.byteslice(0, half), buffer: buf) { |chunk| inflated << chunk }
|
||||
discarded = buf.bytesize
|
||||
buf.clear
|
||||
z.inflate(deflated.byteslice(half..), buffer: buf) { |chunk| inflated << chunk }
|
||||
z.inflate(nil, buffer: buf) { |chunk| inflated << chunk }
|
||||
assert_equal src.bytesize - discarded, inflated.bytesize
|
||||
assert_match(/\A0*\z/, inflated)
|
||||
|
||||
# buffer reallocated between calls, content preserved
|
||||
z = Zlib::Inflate.new
|
||||
buf = String.new(capacity: 1 << 14)
|
||||
inflated = "".dup
|
||||
z.inflate(deflated.byteslice(0, half), buffer: buf) { |chunk| inflated << chunk }
|
||||
pending = buf.dup
|
||||
buf << ("x" * 50_000)
|
||||
buf.replace(pending)
|
||||
z.inflate(deflated.byteslice(half..), buffer: buf) { |chunk| inflated << chunk }
|
||||
z.inflate(nil, buffer: buf) { |chunk| inflated << chunk }
|
||||
assert_equal src, inflated
|
||||
|
||||
# buffer cleared, then continued with Inflate#<< and ZStream#finish
|
||||
z = Zlib::Inflate.new
|
||||
buf = String.new(capacity: 1 << 14)
|
||||
inflated = "".dup
|
||||
z.inflate(deflated.byteslice(0, half), buffer: buf) { |chunk| inflated << chunk }
|
||||
discarded = buf.bytesize
|
||||
buf.clear
|
||||
z << deflated.byteslice(half..)
|
||||
inflated << z.finish
|
||||
assert_equal src.bytesize - discarded, inflated.bytesize
|
||||
assert_match(/\A0*\z/, inflated)
|
||||
end
|
||||
|
||||
def test_inflate_buffer_dropped_while_handling_need_dict
|
||||
dict = "dictionary-" * 4
|
||||
d = Zlib::Deflate.new
|
||||
d.set_dictionary(dict)
|
||||
deflated = d.deflate("A" * 12_000, Zlib::FINISH)
|
||||
|
||||
z = Zlib::Inflate.new
|
||||
buf = String.new(capacity: 1 << 14)
|
||||
assert_raise(Zlib::NeedDict) { z.inflate(deflated, buffer: buf) }
|
||||
buf.clear
|
||||
z.set_dictionary(dict)
|
||||
assert_equal "A" * 12_000, z.inflate("")
|
||||
end
|
||||
|
||||
def test_inflate_buffer_modified_while_handling_need_dict
|
||||
dict = "dictionary-" * 4
|
||||
d = Zlib::Deflate.new
|
||||
d.set_dictionary(dict)
|
||||
deflated = d.deflate("A" * 12_000, Zlib::FINISH)
|
||||
|
||||
# buffer cleared (its heap block is freed) before retrying
|
||||
z = Zlib::Inflate.new
|
||||
buf = String.new(capacity: 1 << 14)
|
||||
assert_raise(Zlib::NeedDict) { z.inflate(deflated, buffer: buf) }
|
||||
buf.clear
|
||||
z.set_dictionary(dict)
|
||||
assert_equal "A" * 12_000, z.inflate("", buffer: buf)
|
||||
|
||||
# buffer grown beyond its capacity (its heap block is reallocated)
|
||||
z = Zlib::Inflate.new
|
||||
buf = String.new(capacity: 1 << 14)
|
||||
assert_raise(Zlib::NeedDict) { z.inflate(deflated, buffer: buf) }
|
||||
buf << ("x" * 20_000)
|
||||
buf.clear
|
||||
z.set_dictionary(dict)
|
||||
assert_equal "A" * 12_000, z.inflate("", buffer: buf)
|
||||
|
||||
# buffer replaced by another string
|
||||
z = Zlib::Inflate.new
|
||||
buf = String.new(capacity: 1 << 14)
|
||||
assert_raise(Zlib::NeedDict) { z.inflate(deflated, buffer: buf) }
|
||||
buf.replace("x" * 20_000)
|
||||
buf.clear
|
||||
z.set_dictionary(dict)
|
||||
assert_equal "A" * 12_000, z.inflate("", buffer: buf)
|
||||
end
|
||||
|
||||
def test_inflate_chunked_break
|
||||
# zeros = Zlib::Deflate.deflate("0" * 100_000)
|
||||
zeros = "x\234\355\3011\001\000\000\000\302\240J\353\237\316\032\036@" \
|
||||
|
||||
Reference in new issue
Block a user