Repository navigation
Conversation
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).
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
The flag stayed set after a call with buffer: had returned its buffer,
so an internal buffer allocated by a later Inflate#<< was treated as
user-provided and never revealed.
data = Zlib::Deflate.deflate("A" * 50000)
half = data.bytesize / 2
inf = Zlib::Inflate.new
inf.inflate(data[0, half], buffer: String.new)
inf << data[half..]
inf.finish.class
#=> [BUG] Segmentation fault
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
In the block form, output not yet yielded stays in the buffer between
calls. It was silently dropped when another buffer (or none) was given
for the next call.
data = Zlib::Deflate.deflate("A" * 50000)
half = data.bytesize / 2
inf = Zlib::Inflate.new
out = +""
inf.inflate(data[0, half], buffer: String.new) { |c| out << c }
inf.inflate(data[half..], buffer: String.new) { |c| out << c }
out << inf.finish
out.bytesize
# expected: 50000
# actual: 47806
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
jeremyevans
approved these changes
Oct 23, 2026
jeremyevans
left a comment
Contributor
There was a problem hiding this comment.
Changes look good. This doesn't seem like it would affect most applications using zlib inflate, as applications tend to either always use buffer or never use buffer for a single inflate. I would guess that mixed use for a single inflate is uncommon.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Zlib::Inflate#inflate(buffer:)did not handle the buffer, or thebuffer:argument itself, changing between calls while the stream was in progress. The worst case is the reported one: if the buffer was modified after a call left the stream in progress (it raisedZlib::NeedDict, or the block form has more input to come), the next run reused the stalenext_outand zlib wrote into freed or reallocated memory.This PR has three commits. Each commit message has a minimal reproduction.
next_out/avail_outare re-derived from the buffer at the start of every run, so#inflate,#<<,#finishand the block form are all covered. They are also reset whenbuffer:is dropped. Code paths withoutbuffer:are unchanged.buffer:returned its buffer, the flag stayed set, so continuing with#<<and#finishreturned a hidden String and crashed.ArgumentErrorin that case would be a reasonable alternative, and I'm happy to switch if you prefer.One visible behavior change from 1: with
buffer:,ZStream#avail_out=no longer changes the chunk size in the block form. Chunks stay at 16 KiB.