Ruby's GzipReader overflow comes from prepending buffered data
CVE-2026-27820 concerns a missing capacity guarantee before GzipReader moves buffered output. The fix belongs to Ruby's zlib gem, with patched releases for different Ruby branches.
Prepending bytes to an existing buffer requires enough storage for both the old bytes and the new ones. Ruby's zlib extension failed to establish that capacity before moving buffered output in Zlib::GzipReader. The March 5 advisory for CVE-2026-27820 describes the resulting buffer overflow and recommends updating the zlib gem. The failure is in the extension's handling of a Ruby string used as native storage.
This distinction matters because zlib names both a Ruby package and the compression library it wraps. The advisory identifies the Ruby gem's zstream_buffer_ungets function. It does not describe a defect that can be resolved merely by assuming the operating system's compression library has been updated. The component carrying the correction is the Ruby zlib gem selected by the application.
I find Ruby's object model easier to follow than the Perl conventions I first used. That consistency is valuable at the application level, but it does not remove native implementation details. A string remains a Ruby object while C extension code uses its backing allocation. This vulnerability occurs at that boundary between the managed object's logical contents and the memory available for a native operation.
Length and capacity describe different properties
The advisory says the affected function prepends caller-provided bytes ahead of previously produced output. It shifts the existing output with memmove, but does not guarantee that the backing string has sufficient capacity. If the required buffer length exceeds the available allocation, the operation can corrupt memory.
The patched 3.2.3 source makes the ordering understandable. Before obtaining the buffer pointer and moving bytes, zstream_buffer_ungets calls zstream_expand_buffer_into with the additional length. It then shifts the existing data forward, copies the new prefix into the vacated space, and updates the string's logical length. Capacity must exist before the shift begins.
A logical string length says how much data the object currently contains. Capacity says how much memory its backing buffer can accommodate. Increasing a length after copying bytes cannot retroactively make an out-of-bounds copy valid. Likewise, memmove permits overlapping source and destination ranges, but it still needs both ranges to fit the available storage. Overlap handling does not establish an allocation's size.
The source also updates the output pointer and remaining output-space bookkeeping after the prepend. Those fields connect the Ruby buffer to the compression stream's view of its writable area. The correction therefore participates in an existing buffer-management protocol. It has to preserve the relationship between the string's data and the stream's subsequent writes.
Reader pushback reaches the native buffer
The patched source connects the helper to GzipReader's pushback operations. ungetc accepts a value that can become a string and forwards its bytes into gzfile_ungets. That function calls the stream prepend helper and tracks the pushed-back amount. ungetbyte reaches the same underlying helper for a single byte.
Pushback is useful when a reader consumes data and then needs to make it available to the next read. It is not a new decompression operation, but it still manipulates the reader's buffered output. This explains why a reader API can require a native memory move even though its public purpose sounds like a simple change in read position.
The advisory does not claim that every gzip file independently triggers this flaw. Its explanation centers on caller-provided bytes and the affected prepend path. Assessing application exposure therefore requires understanding how the application or its libraries use the reader. That is a narrower and more defensible statement than treating all decompression as equivalent to the vulnerable operation.
The disclosed consequence is memory corruption. Neither the source nor the advisory establishes a universal outcome for every affected process, so a crash or arbitrary code execution should not be asserted as inevitable. The concrete defect is sufficient reason to update. Native writes outside an allocated buffer violate the assumptions needed for the Ruby process to continue safely.
Patched versions follow the Ruby branch
The recommended general update is zlib 3.2.3 or later. For older interpreter branches, the advisory provides zlib 3.0.1 for Ruby 3.2 and zlib 3.1.2 for Ruby 3.3, to preserve compatibility with the versions bundled in those series. The older-looking version numbers are backported fixes. They should not be rejected merely because they compare below the newest gem series.
For applications using the current compatible series, the advisory gives this Bundler dependency constraint.
gem "zlib", ">= 3.2.3"
That line is not a universal replacement for the branch-specific guidance. An application on Ruby 3.2 or 3.3 needs the compatible patched line identified by the advisory. The relevant combination includes the interpreter and the gem, with dependency resolution selecting a version the application can actually load.
Outside Bundler, the notice recommends gem update zlib. With Bundler, the application's selected dependency set still determines which implementation runs. Installing a corrected package somewhere on a machine and running an application with a different selected version are different operations. The update needs to reach the execution environment that uses GzipReader.
The abstraction remains useful when its boundary is explicit
My preference for Ruby's readable collection and block syntax remains intact here. The lesson for this event concerns where that readability stops describing the implementation. A high-level reader operation can move native memory, and that operation needs allocation invariants that Ruby callers do not normally see.
The advisory credits calysteon for the report and nobu for the patch. Its fix addresses a concrete ordering requirement in buffer management. For maintained applications, the immediate actionable information is the corrected gem line appropriate to the interpreter. The source explains why the change works by reserving space before existing output moves into it.