Skip to content

Commit 3e9494c

Browse files
committed
Bump zlib version to 3.2.3.
1 parent 0097b87 commit 3e9494c

2 files changed

Lines changed: 110 additions & 26 deletions

File tree

ext/zlib/zlib.c

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0
2626
#endif
2727

28-
#define RUBY_ZLIB_VERSION "3.2.1"
28+
#define RUBY_ZLIB_VERSION "3.2.3"
2929

3030
#ifndef RB_PASS_CALLED_KEYWORDS
3131
# 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)
860860
char *bufptr;
861861
long filled;
862862

863-
if (NIL_P(z->buf) || (long)rb_str_capacity(z->buf) <= ZSTREAM_BUF_FILLED(z)) {
864-
zstream_expand_buffer_into(z, len);
865-
}
863+
zstream_expand_buffer_into(z, len);
866864

867865
RSTRING_GETMEM(z->buf, bufptr, filled);
868866
memmove(bufptr + len, bufptr, filled);
@@ -1094,8 +1092,9 @@ zstream_run_func(struct zstream_run_args *args)
10941092
break;
10951093
}
10961094

1097-
if (err != Z_OK && err != Z_BUF_ERROR)
1095+
if (err != Z_OK && err != Z_BUF_ERROR) {
10981096
break;
1097+
}
10991098

11001099
if (z->stream.avail_out > 0) {
11011100
z->flags |= ZSTREAM_FLAG_IN_STREAM;
@@ -1170,12 +1169,17 @@ zstream_run_try(VALUE value_arg)
11701169
/* retry if no exception is thrown */
11711170
if (err == Z_OK && args->interrupt) {
11721171
args->interrupt = 0;
1173-
goto loop;
1172+
1173+
/* Retry only if both avail_in > 0 (more input to process) and avail_out > 0
1174+
* (output buffer has space). If avail_out == 0, the buffer is full and should
1175+
* be consumed by the caller first. If avail_in == 0, there's nothing more to process. */
1176+
if (z->stream.avail_in > 0 && z->stream.avail_out > 0) {
1177+
goto loop;
1178+
}
11741179
}
11751180

1176-
if (flush != Z_FINISH && err == Z_BUF_ERROR
1177-
&& z->stream.avail_out > 0) {
1178-
z->flags |= ZSTREAM_FLAG_IN_STREAM;
1181+
if (flush != Z_FINISH && err == Z_BUF_ERROR && z->stream.avail_out > 0) {
1182+
z->flags |= ZSTREAM_FLAG_IN_STREAM;
11791183
}
11801184

11811185
zstream_reset_input(z);
@@ -1456,6 +1460,7 @@ rb_zstream_finish(VALUE obj)
14561460
* call-seq:
14571461
* flush_next_in -> input
14581462
*
1463+
* Flushes input buffer and returns all data in that buffer.
14591464
*/
14601465
static VALUE
14611466
rb_zstream_flush_next_in(VALUE obj)
@@ -2444,17 +2449,16 @@ struct gzfile {
24442449

24452450
#define GZFILE_READ_SIZE 2048
24462451

2452+
enum { read_raw_arg_len, read_raw_arg_buf, read_raw_arg__count};
24472453
struct read_raw_arg {
24482454
VALUE io;
2449-
union {
2450-
const VALUE argv[2]; /* for rb_funcallv */
2451-
struct {
2452-
VALUE len;
2453-
VALUE buf;
2454-
} in;
2455-
} as;
2455+
const VALUE argv[read_raw_arg__count]; /* for rb_funcallv */
24562456
};
24572457

2458+
#define read_raw_arg_argc(ra) \
2459+
((int)read_raw_arg__count - NIL_P((ra)->argv[read_raw_arg__count - 1]))
2460+
#define read_raw_arg_init(io, len, buf) { io, { len, buf } }
2461+
24582462
static void
24592463
gzfile_mark(void *p)
24602464
{
@@ -2580,9 +2584,9 @@ gzfile_read_raw_partial(VALUE arg)
25802584
{
25812585
struct read_raw_arg *ra = (struct read_raw_arg *)arg;
25822586
VALUE str;
2583-
int argc = NIL_P(ra->as.argv[1]) ? 1 : 2;
2587+
int argc = read_raw_arg_argc(ra);
25842588

2585-
str = rb_funcallv(ra->io, id_readpartial, argc, ra->as.argv);
2589+
str = rb_funcallv(ra->io, id_readpartial, argc, ra->argv);
25862590
Check_Type(str, T_STRING);
25872591
return str;
25882592
}
@@ -2593,8 +2597,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _)
25932597
struct read_raw_arg *ra = (struct read_raw_arg *)arg;
25942598
VALUE str = Qnil;
25952599
if (rb_obj_is_kind_of(rb_errinfo(), rb_eNoMethodError)) {
2596-
int argc = NIL_P(ra->as.argv[1]) ? 1 : 2;
2597-
str = rb_funcallv(ra->io, id_read, argc, ra->as.argv);
2600+
int argc = read_raw_arg_argc(ra);
2601+
str = rb_funcallv(ra->io, id_read, argc, ra->argv);
25982602
if (!NIL_P(str)) {
25992603
Check_Type(str, T_STRING);
26002604
}
@@ -2605,11 +2609,8 @@ gzfile_read_raw_rescue(VALUE arg, VALUE _)
26052609
static VALUE
26062610
gzfile_read_raw(struct gzfile *gz, VALUE outbuf)
26072611
{
2608-
struct read_raw_arg ra;
2609-
2610-
ra.io = gz->io;
2611-
ra.as.in.len = INT2FIX(GZFILE_READ_SIZE);
2612-
ra.as.in.buf = outbuf;
2612+
struct read_raw_arg ra =
2613+
read_raw_arg_init(gz->io, INT2FIX(GZFILE_READ_SIZE), outbuf);
26132614

26142615
return rb_rescue2(gzfile_read_raw_partial, (VALUE)&ra,
26152616
gzfile_read_raw_rescue, (VALUE)&ra,

test/zlib/test_zlib.rb

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
begin
1010
require 'zlib'
1111
rescue LoadError
12+
else
13+
z = "/zlib.#{RbConfig::CONFIG["DLEXT"]}"
14+
LOADED_ZLIB, = $".select {|f| f.end_with?(z)}
1215
end
1316

1417
if defined? Zlib
@@ -879,6 +882,25 @@ def test_ungetc_at_start_of_file
879882
assert_equal(-1, r.pos, "[ruby-core:81488][Bug #13616]")
880883
end
881884

885+
def test_ungetc_buffer_underflow
886+
initial_bufsize = 1024
887+
payload = "A" * initial_bufsize
888+
gzip_io = StringIO.new
889+
Zlib::GzipWriter.wrap(gzip_io) { |gz| gz.write(payload) }
890+
compressed = gzip_io.string
891+
892+
reader = Zlib::GzipReader.new(StringIO.new(compressed))
893+
reader.read(1)
894+
overflow_bytes = "B" * (initial_bufsize)
895+
reader.ungetc(overflow_bytes)
896+
data = reader.read(overflow_bytes.bytesize)
897+
assert_equal overflow_bytes.bytesize, data.bytesize, data
898+
assert_empty data.delete("B"), data
899+
data = reader.read()
900+
assert_equal initial_bufsize - 1, data.bytesize, data
901+
assert_empty data.delete("A"), data
902+
end
903+
882904
def test_open
883905
Tempfile.create("test_zlib_gzip_reader_open") {|t|
884906
t.close
@@ -1263,6 +1285,36 @@ def test_gzfile_read_size_boundary
12631285
end
12641286
}
12651287
end
1288+
1289+
# Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
1290+
# This reproduces the issue where thread wakeup during GzipReader operations
1291+
# can cause Z_BUF_ERROR to be raised incorrectly
1292+
def test_thread_wakeup_interrupt
1293+
pend 'fails' if RUBY_ENGINE == 'truffleruby'
1294+
content = SecureRandom.base64(5000)
1295+
gzipped = Zlib.gzip(content)
1296+
1297+
1000.times do
1298+
thr = Thread.new do
1299+
loop do
1300+
Zlib::GzipReader.new(StringIO.new(gzipped)).read
1301+
end
1302+
end
1303+
1304+
# Wakeup the thread multiple times to trigger interrupts
1305+
10.times do
1306+
thr.wakeup
1307+
Thread.pass
1308+
end
1309+
1310+
# Give thread a moment to process
1311+
sleep 0.001
1312+
1313+
# Clean up
1314+
thr.kill
1315+
thr.join
1316+
end
1317+
end
12661318
end
12671319

12681320
class TestZlibGzipWriter < Test::Unit::TestCase
@@ -1525,11 +1577,42 @@ def test_gunzip_encoding
15251577
end
15261578

15271579
def test_gunzip_no_memory_leak
1528-
assert_no_memory_leak(%[-rzlib], "#{<<~"{#"}", "#{<<~'};'}")
1580+
assert_no_memory_leak(%W[-r#{LOADED_ZLIB}], "#{<<~"{#"}", "#{<<~'};'}")
15291581
d = Zlib.gzip("data")
15301582
{#
15311583
10_000.times {Zlib.gunzip(d)}
15321584
};
15331585
end
1586+
1587+
# Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
1588+
# This reproduces the issue where thread wakeup during GzipReader operations
1589+
# can cause Z_BUF_ERROR to be raised incorrectly
1590+
def test_thread_wakeup_interrupt
1591+
pend 'fails' if RUBY_ENGINE == 'truffleruby'
1592+
1593+
content = SecureRandom.base64(5000)
1594+
gzipped = Zlib.gzip(content)
1595+
1596+
1000.times do
1597+
thr = Thread.new do
1598+
loop do
1599+
Zlib::GzipReader.new(StringIO.new(gzipped)).read
1600+
end
1601+
end
1602+
1603+
# Wakeup the thread multiple times to trigger interrupts
1604+
10.times do
1605+
thr.wakeup
1606+
Thread.pass
1607+
end
1608+
1609+
# Give thread a moment to process
1610+
sleep 0.001
1611+
1612+
# Clean up
1613+
thr.kill
1614+
thr.join
1615+
end
1616+
end
15341617
end
15351618
end

0 commit comments

Comments
 (0)