From 82d5d26993e9b7eafc79bb1b6be9f39c5db964f9 Mon Sep 17 00:00:00 2001
From: Nobuyoshi Nakada <nobu@ruby-lang.org>
Date: Wed, 26 Nov 2025 22:30:27 +0900
Subject: [PATCH] Fix buffer overflow at ungetc

---
 ext/zlib/zlib.c        |  4 +---
 test/zlib/test_zlib.rb | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+), 3 deletions(-)

--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -798,9 +798,7 @@ zstream_buffer_ungets(struct zstream *z,
     char *bufptr;
     long filled;
 
-    if (NIL_P(z->buf) || (long)rb_str_capacity(z->buf) <= ZSTREAM_BUF_FILLED(z)) {
-	zstream_expand_buffer_into(z, len);
-    }
+    zstream_expand_buffer_into(z, len);
 
     RSTRING_GETMEM(z->buf, bufptr, filled);
     memmove(bufptr + len, bufptr, filled);
--- a/test/zlib/test_zlib.rb
+++ b/test/zlib/test_zlib.rb
@@ -686,6 +686,25 @@ if defined? Zlib
       assert_equal(-1, r.pos, "[ruby-core:81488][Bug #13616]")
     end
 
+    def test_ungetc_buffer_underflow
+      initial_bufsize = 1024
+      payload = "A" * initial_bufsize
+      gzip_io = StringIO.new
+      Zlib::GzipWriter.wrap(gzip_io) { |gz| gz.write(payload) }
+      compressed = gzip_io.string
+
+      reader = Zlib::GzipReader.new(StringIO.new(compressed))
+      reader.read(1)
+      overflow_bytes = "B" * (initial_bufsize)
+      reader.ungetc(overflow_bytes)
+      data = reader.read(overflow_bytes.bytesize)
+      assert_equal overflow_bytes.bytesize, data.bytesize, data
+      assert_empty data.delete("B"), data
+      data = reader.read()
+      assert_equal initial_bufsize - 1, data.bytesize, data
+      assert_empty data.delete("A"), data
+    end
+
     def test_open
       Tempfile.create("test_zlib_gzip_reader_open") {|t|
         t.close
