From 23c0113932407abccddbc6ee5b297d38d2d2bb9c Mon Sep 17 00:00:00 2001 From: koh-sh <34917718+koh-sh@users.noreply.github.com> Date: Sat, 9 Aug 2025 18:30:17 +0900 Subject: [PATCH 1/5] [ruby/stringio] fix: prevent segfault in StringIO#seek with SEEK_END on null device (https://github.com/ruby/stringio/pull/137) Fixes segmentation fault when calling `seek` with `SEEK_END` on null device StringIO created by `StringIO.new(nil)`. ```bash ruby -e "require 'stringio'; StringIO.new(nil).seek(0, IO::SEEK_END)" ``` I tested with below versions. ```bash [koh@Kohs-MacBook-Pro] ~ % ruby -v;gem info stringio;sw_vers ruby 3.4.5 (2025-07-16 revision https://github.com/ruby/stringio/commit/20cda200d3) +PRISM [arm64-darwin24] *** LOCAL GEMS *** stringio (3.1.2) Authors: Nobu Nakada, Charles Oliver Nutter Homepage: https://github.com/ruby/stringio Licenses: Ruby, BSD-2-Clause Installed at (default): /Users/koh/.local/share/mise/installs/ruby/3.4.5/lib/ruby/gems/3.4.0 Pseudo IO on String ProductName: macOS ProductVersion: 15.5 BuildVersion: 24F74 [koh@Kohs-MacBook-Pro] ~ % ``` https://github.com/ruby/stringio/commit/9399747bf9 --- ext/stringio/stringio.c | 6 +++++- test/stringio/test_stringio.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 3003939e10f8e4..d9beb25434df56 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -837,7 +837,11 @@ strio_seek(int argc, VALUE *argv, VALUE self) offset = ptr->pos; break; case 2: - offset = RSTRING_LEN(ptr->string); + if (NIL_P(ptr->string)) { + offset = 0; + } else { + offset = RSTRING_LEN(ptr->string); + } break; default: error_inval("invalid whence"); diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 002b946b6f1b58..4c9cf374253965 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -70,6 +70,16 @@ def test_null assert_nil io.getc end + def test_seek_null + io = StringIO.new(nil) + assert_equal(0, io.seek(0, IO::SEEK_SET)) + assert_equal(0, io.pos) + assert_equal(0, io.seek(0, IO::SEEK_CUR)) + assert_equal(0, io.pos) + assert_equal(0, io.seek(0, IO::SEEK_END)) # This should not segfault + assert_equal(0, io.pos) + end + def test_truncate io = StringIO.new("") io.puts "abc" From 31f2d8990dcebf84cbbd3fedf838babaa59554a3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 19:44:26 +0900 Subject: [PATCH 2/5] [ruby/stringio] Fix SEGV at read/pread on null StringIO https://github.com/ruby/stringio/commit/113dd5a55e --- ext/stringio/stringio.c | 28 +++++++++++++++++++--------- test/stringio/test_stringio.rb | 10 ++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index d9beb25434df56..1e6310d2922a2e 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -203,6 +203,18 @@ check_modifiable(struct StringIO *ptr) } } +static inline bool +outside_p(struct StringIO *ptr, long pos) +{ + return NIL_P(ptr->string) || pos >= RSTRING_LEN(ptr->string); +} + +static inline bool +eos_p(struct StringIO *ptr) +{ + return outside_p(ptr, ptr->pos); +} + static VALUE strio_s_allocate(VALUE klass) { @@ -628,9 +640,8 @@ static struct StringIO * strio_to_read(VALUE self) { struct StringIO *ptr = readable(self); - if (NIL_P(ptr->string)) return NULL; - if (ptr->pos < RSTRING_LEN(ptr->string)) return ptr; - return NULL; + if (eos_p(ptr)) return NULL; + return ptr; } /* @@ -910,7 +921,7 @@ strio_getc(VALUE self) int len; char *p; - if (NIL_P(str) || pos >= RSTRING_LEN(str)) { + if (eos_p(ptr)) { return Qnil; } p = RSTRING_PTR(str)+pos; @@ -931,7 +942,7 @@ strio_getbyte(VALUE self) { struct StringIO *ptr = readable(self); int c; - if (NIL_P(ptr->string) || ptr->pos >= RSTRING_LEN(ptr->string)) { + if (eos_p(ptr)) { return Qnil; } c = RSTRING_PTR(ptr->string)[ptr->pos++]; @@ -1609,10 +1620,9 @@ strio_read(int argc, VALUE *argv, VALUE self) if (len < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); } - if (len > 0 && - (NIL_P(ptr->string) || ptr->pos >= RSTRING_LEN(ptr->string))) { + if (eos_p(ptr)) { if (!NIL_P(str)) rb_str_resize(str, 0); - return Qnil; + return len > 0 ? Qnil : rb_str_new(0, 0); } binary = 1; break; @@ -1688,7 +1698,7 @@ strio_pread(int argc, VALUE *argv, VALUE self) struct StringIO *ptr = readable(self); - if (offset >= RSTRING_LEN(ptr->string)) { + if (outside_p(ptr, offset)) { rb_eof_error(); } diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 4c9cf374253965..8b5ab37657ddec 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -70,6 +70,16 @@ def test_null assert_nil io.getc end + def test_pread_null + io = StringIO.new(nil) + assert_raise(EOFError) { io.pread(1, 0) } + end + + def test_read_null + io = StringIO.new(nil) + assert_equal "", io.read(0) + end + def test_seek_null io = StringIO.new(nil) assert_equal(0, io.seek(0, IO::SEEK_SET)) From b4d5ebcd12418642dec9f1e2e73ac87495666262 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 19:45:25 +0900 Subject: [PATCH 3/5] [ruby/stringio] Fix SEGV at eof? on null StringIO https://github.com/ruby/stringio/commit/29b9133332 --- test/stringio/test_stringio.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 8b5ab37657ddec..5215a6d31273dd 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -70,6 +70,11 @@ def test_null assert_nil io.getc end + def test_pread_eof + io = StringIO.new(nil) + assert_predicate io, :eof? + end + def test_pread_null io = StringIO.new(nil) assert_raise(EOFError) { io.pread(1, 0) } From 77b3495e979ceb597e86d255d3d647b8d5b43a9e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 20:15:53 +0900 Subject: [PATCH 4/5] [ruby/stringio] Adjust indent [ci skip] https://github.com/ruby/stringio/commit/ac6292c17f --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 1e6310d2922a2e..0493c8cd50856b 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1935,7 +1935,7 @@ Init_stringio(void) #undef rb_intern #ifdef HAVE_RB_EXT_RACTOR_SAFE - rb_ext_ractor_safe(true); + rb_ext_ractor_safe(true); #endif VALUE StringIO = rb_define_class("StringIO", rb_cObject); From 2a6345e957c01f4495323723c7a3d7ac0d4ac339 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 9 Aug 2025 10:54:22 +0200 Subject: [PATCH 5/5] time.c: fix time_mark_and_move when WIDEVALUE_IS_WIDER In such case the pointer need to be casted. --- time.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/time.c b/time.c index 7159a9309840de..95df85db9359a4 100644 --- a/time.c +++ b/time.c @@ -1891,8 +1891,8 @@ static void time_mark_and_move(void *ptr) { struct time_object *tobj = ptr; - if (!FIXWV_P(tobj->timew)) { - rb_gc_mark_and_move(&WIDEVAL_GET(tobj->timew)); + if (!WIDEVALUE_IS_WIDER || !FIXWV_P(tobj->timew)) { + rb_gc_mark_and_move((VALUE *)&WIDEVAL_GET(tobj->timew)); } rb_gc_mark_and_move(&tobj->vtm.year); rb_gc_mark_and_move(&tobj->vtm.subsecx);