From 46ca8fb923b7dbe85c901dfdd573fd4a56223e8e Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 30 Sep 2024 19:34:36 +0800 Subject: [PATCH] Fix main stack top detection on musl-libc (#15047) Stack overflow detection relies on the current thread's stack bounds. On Linux this is obtained using `LibC.pthread_getattr_np` and `LibC.pthread_attr_getstack`. However, on musl-libc the internal stack size is always [hardcoded](https://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_create.c?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n267) to a very small [default](https://git.musl-libc.org/cgit/musl/tree/src/internal/pthread_impl.h?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n197) of [128 KiB](https://git.musl-libc.org/cgit/musl/tree/src/thread/default_attr.c?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n3), and [`pthread_getattr_np` returns this value unmodified](https://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_getattr_np.c?id=dd1e63c3638d5f9afb857fccf6ce1415ca5f1b8b#n13), presumably for the main thread as well. The result is that the main thread has an entirely incorrect `@stack`, as it respects `ulimit -s`, and that non-main threads are really that small, as we don't pass any `attr` to `GC.pthread_create` on thread creation. [This is also mentioned on Ruby's bug tracker](https://bugs.ruby-lang.org/issues/14387#change-70914). --- spec/std/kernel_spec.cr | 23 ++++++--------- src/crystal/system/unix/pthread.cr | 29 +++++++++++++++++-- .../aarch64-linux-musl/c/sys/resource.cr | 11 +++++++ src/lib_c/i386-linux-musl/c/sys/resource.cr | 11 +++++++ src/lib_c/x86_64-linux-musl/c/sys/resource.cr | 11 +++++++ 5 files changed, 68 insertions(+), 17 deletions(-) diff --git a/spec/std/kernel_spec.cr b/spec/std/kernel_spec.cr index 149e6385ac97..f41529af901a 100644 --- a/spec/std/kernel_spec.cr +++ b/spec/std/kernel_spec.cr @@ -254,16 +254,12 @@ describe "hardware exception" do error.should_not contain("Stack overflow") end - {% if flag?(:musl) %} - # FIXME: Pending as mitigation for https://github.com/crystal-lang/crystal/issues/7482 - pending "detects stack overflow on the main stack" - {% else %} - it "detects stack overflow on the main stack", tags: %w[slow] do - # This spec can take some time under FreeBSD where - # the default stack size is 0.5G. Setting a - # smaller stack size with `ulimit -s 8192` - # will address this. - status, _, error = compile_and_run_source <<-'CRYSTAL' + it "detects stack overflow on the main stack", tags: %w[slow] do + # This spec can take some time under FreeBSD where + # the default stack size is 0.5G. Setting a + # smaller stack size with `ulimit -s 8192` + # will address this. + status, _, error = compile_and_run_source <<-'CRYSTAL' def foo y = StaticArray(Int8, 512).new(0) foo @@ -271,10 +267,9 @@ describe "hardware exception" do foo CRYSTAL - status.success?.should be_false - error.should contain("Stack overflow") - end - {% end %} + status.success?.should be_false + error.should contain("Stack overflow") + end it "detects stack overflow on a fiber stack", tags: %w[slow] do status, _, error = compile_and_run_source <<-'CRYSTAL' diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 952843f4c2b0..50a0fc56e818 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -131,11 +131,26 @@ module Crystal::System::Thread ret = LibC.pthread_attr_destroy(pointerof(attr)) raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 {% elsif flag?(:linux) %} - if LibC.pthread_getattr_np(@system_handle, out attr) == 0 - LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _) - end + ret = LibC.pthread_getattr_np(@system_handle, out attr) + raise RuntimeError.from_os_error("pthread_getattr_np", Errno.new(ret)) unless ret == 0 + + LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out stack_size) + ret = LibC.pthread_attr_destroy(pointerof(attr)) raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 + + # with musl-libc, the main thread does not respect `rlimit -Ss` and + # instead returns the same default stack size as non-default threads, so + # we obtain the rlimit to correct the stack address manually + {% if flag?(:musl) %} + if Thread.current_is_main? + if LibC.getrlimit(LibC::RLIMIT_STACK, out rlim) == 0 + address = address + stack_size - rlim.rlim_cur + else + raise RuntimeError.from_errno("getrlimit") + end + end + {% end %} {% elsif flag?(:openbsd) %} ret = LibC.pthread_stackseg_np(@system_handle, out stack) raise RuntimeError.from_os_error("pthread_stackseg_np", Errno.new(ret)) unless ret == 0 @@ -153,6 +168,14 @@ module Crystal::System::Thread address end + {% if flag?(:musl) %} + @@main_handle : Handle = current_handle + + def self.current_is_main? + current_handle == @@main_handle + end + {% end %} + # Warning: must be called from the current thread itself, because Darwin # doesn't allow to set the name of any thread but the current one! private def system_name=(name : String) : String diff --git a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr index 7f550c37a622..daa583ac5895 100644 --- a/src/lib_c/aarch64-linux-musl/c/sys/resource.cr +++ b/src/lib_c/aarch64-linux-musl/c/sys/resource.cr @@ -1,4 +1,15 @@ lib LibC + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + fun getrlimit(Int, Rlimit*) : Int + + RLIMIT_STACK = 3 + struct RUsage ru_utime : Timeval ru_stime : Timeval diff --git a/src/lib_c/i386-linux-musl/c/sys/resource.cr b/src/lib_c/i386-linux-musl/c/sys/resource.cr index 7f550c37a622..daa583ac5895 100644 --- a/src/lib_c/i386-linux-musl/c/sys/resource.cr +++ b/src/lib_c/i386-linux-musl/c/sys/resource.cr @@ -1,4 +1,15 @@ lib LibC + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + fun getrlimit(Int, Rlimit*) : Int + + RLIMIT_STACK = 3 + struct RUsage ru_utime : Timeval ru_stime : Timeval diff --git a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr index 7f550c37a622..daa583ac5895 100644 --- a/src/lib_c/x86_64-linux-musl/c/sys/resource.cr +++ b/src/lib_c/x86_64-linux-musl/c/sys/resource.cr @@ -1,4 +1,15 @@ lib LibC + alias RlimT = ULongLong + + struct Rlimit + rlim_cur : RlimT + rlim_max : RlimT + end + + fun getrlimit(Int, Rlimit*) : Int + + RLIMIT_STACK = 3 + struct RUsage ru_utime : Timeval ru_stime : Timeval