From 154e828cd347cf5d4aaff94ad1bae5918ce7b7c2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 11 Apr 2025 08:51:07 +0900 Subject: [PATCH 1/5] Prefer `th->ec` for stack base/size. --- thread_pthread.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/thread_pthread.c b/thread_pthread.c index 993437f02b1183..195fe69948780a 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -3149,8 +3149,12 @@ ruby_stack_overflowed_p(const rb_thread_t *th, const void *addr) const size_t water_mark = 1024 * 1024; STACK_GROW_DIR_DETECTION; + if (th) { + size = th->ec->machine.stack_maxsize; + base = (char *)th->ec->machine.stack_start - STACK_DIR_UPPER(0, size); + } #ifdef STACKADDR_AVAILABLE - if (get_stack(&base, &size) == 0) { + else if (get_stack(&base, &size) == 0) { # ifdef __APPLE__ if (pthread_equal(th->nt->thread_id, native_main_thread.id)) { struct rlimit rlim; @@ -3161,15 +3165,11 @@ ruby_stack_overflowed_p(const rb_thread_t *th, const void *addr) # endif base = (char *)base + STACK_DIR_UPPER(+size, -size); } - else #endif - if (th) { - size = th->ec->machine.stack_maxsize; - base = (char *)th->ec->machine.stack_start - STACK_DIR_UPPER(0, size); - } else { return 0; } + size /= RUBY_STACK_SPACE_RATIO; if (size > water_mark) size = water_mark; if (IS_STACK_DIR_UPPER()) { From 89fa2d93dc5712444ea8a38ac8191bb712c41c6c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 16 Apr 2025 22:24:35 +0900 Subject: [PATCH 2/5] Add tests. --- ext/-test-/stack/extconf.rb | 3 ++ ext/-test-/stack/stack.c | 24 ++++++++++++++++ test/-ext-/stack/test_stack_overflow.rb | 37 +++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 ext/-test-/stack/extconf.rb create mode 100644 ext/-test-/stack/stack.c create mode 100644 test/-ext-/stack/test_stack_overflow.rb diff --git a/ext/-test-/stack/extconf.rb b/ext/-test-/stack/extconf.rb new file mode 100644 index 00000000000000..d786b15db98c7f --- /dev/null +++ b/ext/-test-/stack/extconf.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: false +require_relative "../auto_ext.rb" +auto_ext(inc: true) diff --git a/ext/-test-/stack/stack.c b/ext/-test-/stack/stack.c new file mode 100644 index 00000000000000..d69ca4da8413dd --- /dev/null +++ b/ext/-test-/stack/stack.c @@ -0,0 +1,24 @@ +#include "ruby.h" +#include "internal/string.h" + +static VALUE +stack_alloca_overflow(VALUE self) +{ + size_t i = 0; + + while (1) { + // Allocate and touch memory to force actual stack usage: + volatile char *stack = alloca(1024); + stack[0] = (char)i; + stack[1023] = (char)i; + i++; + } + + return Qnil; +} + +void +Init_stack(VALUE klass) +{ + rb_define_singleton_method(rb_cThread, "alloca_overflow", stack_alloca_overflow, 0); +} diff --git a/test/-ext-/stack/test_stack_overflow.rb b/test/-ext-/stack/test_stack_overflow.rb new file mode 100644 index 00000000000000..78282fd128837b --- /dev/null +++ b/test/-ext-/stack/test_stack_overflow.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true +require 'test/unit' +require '-test-/stack' + +class Test_StackOverflow < Test::Unit::TestCase + # def test_proc_overflow + # overflow_proc = proc do + # Thread.alloca_overflow + # end + + # assert_raise(SystemStackError) do + # overflow_proc.call + # end + # end + + def test_thread_stack_overflow + thread = Thread.new do + Thread.current.report_on_exception = false + + Thread.alloca_overflow + end + + assert_raise(SystemStackError) do + thread.join + end + end + + def test_fiber_stack_overflow + fiber = Fiber.new do + Thread.alloca_overflow + end + + assert_raise(SystemStackError) do + fiber.resume + end + end +end From a4cdcb804057f1034b64929c443cceb6247d52e7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 17 Apr 2025 12:22:28 +0900 Subject: [PATCH 3/5] Use `assert_separately`. --- test/-ext-/stack/test_stack_overflow.rb | 50 ++++++++++++++----------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/test/-ext-/stack/test_stack_overflow.rb b/test/-ext-/stack/test_stack_overflow.rb index 78282fd128837b..f9eaad0a870236 100644 --- a/test/-ext-/stack/test_stack_overflow.rb +++ b/test/-ext-/stack/test_stack_overflow.rb @@ -1,37 +1,43 @@ # frozen_string_literal: true require 'test/unit' -require '-test-/stack' class Test_StackOverflow < Test::Unit::TestCase - # def test_proc_overflow - # overflow_proc = proc do - # Thread.alloca_overflow - # end + def test_proc_overflow + assert_separately([], <<~RUBY) + require '-test-/stack' - # assert_raise(SystemStackError) do - # overflow_proc.call - # end - # end + assert_raise(SystemStackError) do + Thread.alloca_overflow + end + RUBY + end def test_thread_stack_overflow - thread = Thread.new do - Thread.current.report_on_exception = false + assert_separately([], <<~RUBY) + require '-test-/stack' - Thread.alloca_overflow - end + thread = Thread.new do + Thread.current.report_on_exception = false + Thread.alloca_overflow + end - assert_raise(SystemStackError) do - thread.join - end + assert_raise(SystemStackError) do + thread.join + end + RUBY end def test_fiber_stack_overflow - fiber = Fiber.new do - Thread.alloca_overflow - end + assert_separately([], <<~RUBY) + require '-test-/stack' + + fiber = Fiber.new do + Thread.alloca_overflow + end - assert_raise(SystemStackError) do - fiber.resume - end + assert_raise(SystemStackError) do + fiber.resume + end + RUBY end end From 00d4f386a358648673ede01539004dfde5197588 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 17 Apr 2025 16:08:44 +0900 Subject: [PATCH 4/5] Skip windows. --- test/-ext-/stack/test_stack_overflow.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/-ext-/stack/test_stack_overflow.rb b/test/-ext-/stack/test_stack_overflow.rb index f9eaad0a870236..7c0b390434429b 100644 --- a/test/-ext-/stack/test_stack_overflow.rb +++ b/test/-ext-/stack/test_stack_overflow.rb @@ -3,6 +3,8 @@ class Test_StackOverflow < Test::Unit::TestCase def test_proc_overflow + omit("Windows stack overflow handling is missing") if RUBY_PLATFORM =~ /mswin|win32|mingw/ + assert_separately([], <<~RUBY) require '-test-/stack' @@ -13,6 +15,8 @@ def test_proc_overflow end def test_thread_stack_overflow + omit("Windows stack overflow handling is missing") if RUBY_PLATFORM =~ /mswin|win32|mingw/ + assert_separately([], <<~RUBY) require '-test-/stack' @@ -28,6 +32,8 @@ def test_thread_stack_overflow end def test_fiber_stack_overflow + omit("Windows stack overflow handling is missing") if RUBY_PLATFORM =~ /mswin|win32|mingw/ + assert_separately([], <<~RUBY) require '-test-/stack' From 189cbab9164ced2d17a4f5c0f508beb027b3a997 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 17 Apr 2025 18:00:27 +0900 Subject: [PATCH 5/5] Disable GC during alloca test. --- test/-ext-/stack/test_stack_overflow.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/-ext-/stack/test_stack_overflow.rb b/test/-ext-/stack/test_stack_overflow.rb index 7c0b390434429b..eadf6d292a0391 100644 --- a/test/-ext-/stack/test_stack_overflow.rb +++ b/test/-ext-/stack/test_stack_overflow.rb @@ -6,6 +6,8 @@ def test_proc_overflow omit("Windows stack overflow handling is missing") if RUBY_PLATFORM =~ /mswin|win32|mingw/ assert_separately([], <<~RUBY) + # GC may try to scan the top of the stack and cause a SEGV. + GC.disable require '-test-/stack' assert_raise(SystemStackError) do @@ -19,6 +21,7 @@ def test_thread_stack_overflow assert_separately([], <<~RUBY) require '-test-/stack' + GC.disable thread = Thread.new do Thread.current.report_on_exception = false @@ -36,6 +39,7 @@ def test_fiber_stack_overflow assert_separately([], <<~RUBY) require '-test-/stack' + GC.disable fiber = Fiber.new do Thread.alloca_overflow