From 42088a1c1452ae29e755e1fac67dc6bdcf06bbc3 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Sun, 25 Oct 2026 05:11:32 +0900 Subject: [PATCH] Detect a separator modified during Zlib::GzipReader#gets rscheck required both the pointer and the length to change, so a separator reallocated to the same length was not detected and the freed pointer was read. rs = "-" * 1000 src = StringIO.new(Zlib.gzip(Random.bytes(200_000).tr("-", "x") + rs)) n = 0 io = Object.new io.define_singleton_method(:readpartial) do |len, buf = nil| (rs.clear << "-" * 1000) if (n += 1) == 300 src.readpartial(100, *buf) end Zlib::GzipReader.new(io).gets(rs) #=> heap-use-after-free READ (with ASAN) Co-Authored-By: Claude Opus 5.5 --- ext/zlib/zlib.c | 2 +- test/zlib/test_zlib.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index f13ef4d..966c280 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -4338,7 +4338,7 @@ gzreader_skip_linebreaks(struct gzfile *gz) static void rscheck(const char *rsptr, rb_len_t rslen, VALUE rs) { - if (RSTRING_PTR(rs) != rsptr && RSTRING_LEN(rs) != rslen) + if (RSTRING_PTR(rs) != rsptr || RSTRING_LEN(rs) != rslen) rb_raise(rb_eRuntimeError, "rs modified"); } diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index e1ecbf8..2a985ec 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -987,6 +987,19 @@ def test_read0 assert_equal("", Zlib::GzipReader.new(StringIO.new(D0)).read(0)) end + def test_gets_separator_modified_by_io + rs = "-" * 1000 + src = StringIO.new(Zlib.gzip(Random.new(0).bytes(200_000).tr("-", "x") + rs)) + n = 0 + io = Object.new + io.define_singleton_method(:readpartial) do |len, buf = nil| + (rs.clear << "-" * 1000) if (n += 1) == 300 + src.readpartial([len, 100].min, *buf) + end + gz = Zlib::GzipReader.new(io) + assert_raise_with_message(RuntimeError, "rs modified") { gz.gets(rs) } + end + def test_ungetc s = "".dup w = Zlib::GzipWriter.new(StringIO.new(s))