summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornagachika <nagachika@ruby-lang.org>2026-03-07 14:13:14 +0900
committernagachika <nagachika@ruby-lang.org>2026-03-07 14:50:06 +0900
commit3e9494c70bb7b23a5982e8b21087938e8a753b91 (patch)
tree1c4e851acedfe23eb3d5164fec63a2b291fe6665
parent0097b87b1e2c6aa60489527e421e8bf2e2791d69 (diff)
Bump zlib version to 3.2.3.
-rw-r--r--ext/zlib/zlib.c51
-rw-r--r--test/zlib/test_zlib.rb85
2 files changed, 110 insertions, 26 deletions
diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c
index 0b9c4d65ee..481d74b2b6 100644
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -25,7 +25,7 @@
# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0
#endif
-#define RUBY_ZLIB_VERSION "3.2.1"
+#define RUBY_ZLIB_VERSION "3.2.3"
#ifndef RB_PASS_CALLED_KEYWORDS
# define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass)
@@ -860,9 +860,7 @@ zstream_buffer_ungets(struct zstream *z, const Bytef *b, unsigned long len)
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);
@@ -1094,8 +1092,9 @@ zstream_run_func(struct zstream_run_args *args)
break;
}
- if (err != Z_OK && err != Z_BUF_ERROR)
+ if (err != Z_OK && err != Z_BUF_ERROR) {
break;
+ }
if (z->stream.avail_out > 0) {
z->flags |= ZSTREAM_FLAG_IN_STREAM;
@@ -1170,12 +1169,17 @@ loop:
/* retry if no exception is thrown */
if (err == Z_OK && args->interrupt) {
args->interrupt = 0;
- goto loop;
+
+ /* Retry only if both avail_in > 0 (more input to process) and avail_out > 0
+ * (output buffer has space). If avail_out == 0, the buffer is full and should
+ * be consumed by the caller first. If avail_in == 0, there's nothing more to process. */
+ if (z->stream.avail_in > 0 && z->stream.avail_out > 0) {
+ goto loop;
+ }
}
- if (flush != Z_FINISH && err == Z_BUF_ERROR
- && z->stream.avail_out > 0) {
- z->flags |= ZSTREAM_FLAG_IN_STREAM;
+ if (flush != Z_FINISH && err == Z_BUF_ERROR && z->stream.avail_out > 0) {
+ z->flags |= ZSTREAM_FLAG_IN_STREAM;
}
zstream_reset_input(z);
@@ -1456,6 +1460,7 @@ rb_zstream_finish(VALUE obj)
* call-seq:
* flush_next_in -> input
*
+ * Flushes input buffer and returns all data in that buffer.
*/
static VALUE
rb_zstream_flush_next_in(VALUE obj)
@@ -2444,17 +2449,16 @@ struct gzfile {
#define GZFILE_READ_SIZE 2048
+enum { read_raw_arg_len, read_raw_arg_buf, read_raw_arg__count};
struct read_raw_arg {
VALUE io;
- union {
- const VALUE argv[2]; /* for rb_funcallv */
- struct {
- VALUE len;
- VALUE buf;
- } in;
- } as;
+ const VALUE argv[read_raw_arg__count]; /* for rb_funcallv */
};
+#define read_raw_arg_argc(ra) \
+ ((int)read_raw_arg__count - NIL_P((ra)->argv[read_raw_arg__count - 1]))
+#define read_raw_arg_init(io, len, buf) { io, { len, buf } }
+
static void
gzfile_mark(void *p)
{
@@ -2580,9 +2584,9 @@ gzfile_read_raw_partial(VALUE arg)
{
struct read_raw_arg *ra = (struct read_raw_arg *)arg;
VALUE str;
- int argc = NIL_P(ra->as.argv[1]) ? 1 : 2;
+ int argc = read_raw_arg_argc(ra);
- str = rb_funcallv(ra->io, id_readpartial, argc, ra->as.argv);
+ str = rb_funcallv(ra->io, id_readpartial, argc, ra->argv);
Check_Type(str, T_STRING);
return str;
}
@@ -2593,8 +2597,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _)
struct read_raw_arg *ra = (struct read_raw_arg *)arg;
VALUE str = Qnil;
if (rb_obj_is_kind_of(rb_errinfo(), rb_eNoMethodError)) {
- int argc = NIL_P(ra->as.argv[1]) ? 1 : 2;
- str = rb_funcallv(ra->io, id_read, argc, ra->as.argv);
+ int argc = read_raw_arg_argc(ra);
+ str = rb_funcallv(ra->io, id_read, argc, ra->argv);
if (!NIL_P(str)) {
Check_Type(str, T_STRING);
}
@@ -2605,11 +2609,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _)
static VALUE
gzfile_read_raw(struct gzfile *gz, VALUE outbuf)
{
- struct read_raw_arg ra;
-
- ra.io = gz->io;
- ra.as.in.len = INT2FIX(GZFILE_READ_SIZE);
- ra.as.in.buf = outbuf;
+ struct read_raw_arg ra =
+ read_raw_arg_init(gz->io, INT2FIX(GZFILE_READ_SIZE), outbuf);
return rb_rescue2(gzfile_read_raw_partial, (VALUE)&ra,
gzfile_read_raw_rescue, (VALUE)&ra,
diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb
index 5a8463ad8e..48b8f172ff 100644
--- a/test/zlib/test_zlib.rb
+++ b/test/zlib/test_zlib.rb
@@ -9,6 +9,9 @@ require 'securerandom'
begin
require 'zlib'
rescue LoadError
+else
+ z = "/zlib.#{RbConfig::CONFIG["DLEXT"]}"
+ LOADED_ZLIB, = $".select {|f| f.end_with?(z)}
end
if defined? Zlib
@@ -879,6 +882,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
@@ -1263,6 +1285,36 @@ if defined? Zlib
end
}
end
+
+ # Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
+ # This reproduces the issue where thread wakeup during GzipReader operations
+ # can cause Z_BUF_ERROR to be raised incorrectly
+ def test_thread_wakeup_interrupt
+ pend 'fails' if RUBY_ENGINE == 'truffleruby'
+ content = SecureRandom.base64(5000)
+ gzipped = Zlib.gzip(content)
+
+ 1000.times do
+ thr = Thread.new do
+ loop do
+ Zlib::GzipReader.new(StringIO.new(gzipped)).read
+ end
+ end
+
+ # Wakeup the thread multiple times to trigger interrupts
+ 10.times do
+ thr.wakeup
+ Thread.pass
+ end
+
+ # Give thread a moment to process
+ sleep 0.001
+
+ # Clean up
+ thr.kill
+ thr.join
+ end
+ end
end
class TestZlibGzipWriter < Test::Unit::TestCase
@@ -1525,11 +1577,42 @@ if defined? Zlib
end
def test_gunzip_no_memory_leak
- assert_no_memory_leak(%[-rzlib], "#{<<~"{#"}", "#{<<~'};'}")
+ assert_no_memory_leak(%W[-r#{LOADED_ZLIB}], "#{<<~"{#"}", "#{<<~'};'}")
d = Zlib.gzip("data")
{#
10_000.times {Zlib.gunzip(d)}
};
end
+
+ # Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
+ # This reproduces the issue where thread wakeup during GzipReader operations
+ # can cause Z_BUF_ERROR to be raised incorrectly
+ def test_thread_wakeup_interrupt
+ pend 'fails' if RUBY_ENGINE == 'truffleruby'
+
+ content = SecureRandom.base64(5000)
+ gzipped = Zlib.gzip(content)
+
+ 1000.times do
+ thr = Thread.new do
+ loop do
+ Zlib::GzipReader.new(StringIO.new(gzipped)).read
+ end
+ end
+
+ # Wakeup the thread multiple times to trigger interrupts
+ 10.times do
+ thr.wakeup
+ Thread.pass
+ end
+
+ # Give thread a moment to process
+ sleep 0.001
+
+ # Clean up
+ thr.kill
+ thr.join
+ end
+ end
end
end