diff options
| author | nagachika <nagachika@ruby-lang.org> | 2026-03-07 14:13:14 +0900 |
|---|---|---|
| committer | nagachika <nagachika@ruby-lang.org> | 2026-03-07 14:50:06 +0900 |
| commit | 3e9494c70bb7b23a5982e8b21087938e8a753b91 (patch) | |
| tree | 1c4e851acedfe23eb3d5164fec63a2b291fe6665 | |
| parent | 0097b87b1e2c6aa60489527e421e8bf2e2791d69 (diff) | |
Bump zlib version to 3.2.3.
| -rw-r--r-- | ext/zlib/zlib.c | 51 | ||||
| -rw-r--r-- | test/zlib/test_zlib.rb | 85 |
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 |
