Bug #7141

ALT_STACK_SIZE is not enough

Added by Narihiro Nakamura over 1 year ago. Updated over 1 year ago.

[ruby-dev:46213]
Status:Closed
Priority:Normal
Assignee:Motohiro KOSAKI
Category:core
Target version:2.0.0
ruby -v:ruby 2.0.0dev (2012-10-05 trunk 36982) [x86_64-linux] Backport:

Description

nariです。

以下で教えていただいたバックトレースが出ない件をもう少し追いかけてみた
ところ、どうもシグナルハンドラ内でスタックオーバーフローしているような
気がしています。

http://bugs.ruby-lang.org/issues/7095#note-6

r37088 のコミットで一応問題は再現しなくなったのですが、たぶんこれはスタッ
クを突き破ってメモリ破壊したときに、たまたまセグメント違反にならないよ
うなメモリの配置になったためだと思われます。

以下のようにaltstackの周りをmprotectして確認したところLinux64bit環境で
もSEGVが発生しました。

--- パッチ ---
diff --git a/gc.c b/gc.c
index f1f7aaa..dbe3c3d 100644
--- a/gc.c
+++ b/gc.c
@@ -588,6 +588,8 @@ addheapslots(rbobjspacet *objspace, sizet add)
heaps
inc = 0;
}

+#include
+
static void
initheap(rbobjspacet *objspace)
{
@@ -599,7 +601,17 @@ init
heap(rbobjspacet objspace)
/
altstack of another threads are allocated in another place /
rbthreadt *th = GETTHREAD();
void *tmp = th->altstack;
- th->altstack = malloc(ALT
STACKSIZE);
+ VALUE atmp = 0;
+ th->altstack = mmap(NULL, ALT
STACKSIZE+80000,
+ PROT
READ | PROTWRITE,
+ MAP
PRIVATE | MAPANONYMOUS,
+ -1, 0);
+ th->altstack = ((VALUE)th->altstack)+40000;
+ mprotect((void *)(((VALUE)th->altstack)-40000), 40000, PROT
NONE);
+ mprotect((void *)(((VALUE)th->altstack)+ALTSTACKSIZE), 40000, PROTNONE);
+ atmp = (VALUE)th->altstack;
+ fprintf(stderr, "altstack: %p-%p...%p-%p\n",
+ atmp-4000, atmp, atmp+ALT
STACKSIZE, atmp+ALTSTACK_SIZE+4000);
free(tmp); /
free previously allocated area /
}
#endif
diff --git a/vm.c b/vm.c
index ae201dc..95d8202 100644
--- a/vm.c
+++ b/vm.c
@@ -1736,7 +1736,7 @@ threadfree(void *ptr)
else {
#ifdef USE
SIGALTSTACK
if (th->altstack) {
- free(th->altstack);
+ /
free(th->altstack); */
}
#endif
ruby_xfree(ptr);
--- ここまで ---

パッチを当てた後に./minirubyを実行。

% ./miniruby -e 'Process.kill :SEGV, $$'
altstack: 0x7fd3f23a7000-0x7fd3f23a7190...0x7fd3f23a8190-0x7fd3f23a8320
-e:1: [BUG] Segmentation fault
ruby 2.0.0dev (2012-10-05 trunk 36982) [x86_64-linux]

-- Control frame information -----------------------------------------------
zsh: segmentation fault (core dumped) ./miniruby -e 'Process.kill :SEGV, $$'

% ./miniruby -v
altstack: 0x7f7ee72fb000-0x7f7ee72fb190...0x7f7ee72fc190-0x7f7ee72fc320
ruby 2.0.0dev (2012-10-05 trunk 36982) [x86_64-linux]

以下はgdbの抜粋です。

% gdb ./miniruby
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2) 7.4-2012.04
...
(gdb) r -e 'Process.kill :SEGV, $$'
...
Continuing.
-e:1: [BUG] Segmentation fault
ruby 2.0.0dev (2012-10-05 trunk 36982) [x86_64-linux]

-- Control frame information -----------------------------------------------

Program received signal SIGSEGV, Segmentation fault.
bufferedvfprintf (s=0x7ffff747c180, format=0x5555557358af "c:%04td ", args=0x7ffff7ff76a8) at vfprintf.c:2313
2313 vfprintf.c: No such file or directory.
(gdb) bt
#0 buffered
vfprintf (s=0x7ffff747c180, format=0x5555557358af "c:%04td ", args=0x7ffff7ff76a8) at vfprintf.c:2313
#1 0x00007ffff710bbfe in IOvfprintfinternal (s=0x7ffff747c180, format=0x5555557358af "c:%04td ", ap=0x7ffff7ff76a8) at vfprintf.c:1316
#2 0x00007ffff7116857 in _
fprintf (stream=, format=) at fprintf.c:33
#3 0x00005555556fad8a in controlframedump (th=0x5555559de530, cfp=0x7ffff7fcff08) at vmdump.c:111
#4 0x00005555556fafbf in rb
vmdebugstackdumpraw (th=0x5555559de530, cfp=0x7ffff7fcff08) at vmdump.c:163
#5 0x00005555556fb626 in rbvmbugreport () at vmdump.c:610
#6 0x00005555555b1599 in report
bug (file=0x555555a4dad8 "-e", line=1, fmt=0x55555572ebaf "Segmentation fault", args=0x7ffff7ff7b58) at error.c:306
#7 0x00005555555b16b6 in rb_bug (fmt=0x55555572ebaf "Segmentation fault") at error.c:325
#8 0x000055555568085e in sigsegv (sig=11, info=0x7ffff7ff7db0, ctx=0x7ffff7ff7c80) at signal.c:607
#9
....
(gdb) i f 0
Stack frame at 0x7ffff7ff7030:
...
Locals at 0x7ffff7ff4ed8, Previous frame's sp is 0x7ffff7ff7030
...
(gdb) i f 8
Stack frame at 0x7ffff7ff7c80:
...
(gdb) p 0x7ffff7ff4ed8 - 0x7ffff7ff7c80
$1 = -11688

一番先頭のフレームのLocalsが0x7ffff7ff4ed8で末尾が0x7ffff7ff7c80なので
どうもスタックオーバーフローっぽいのですがどうでしょう…。

とりあえず、ALTSTACKSIZEを5倍くらいすると現象は再現しなくなりました。

diff --git a/vmcore.h b/vmcore.h
index 8d51407..13f12d4 100644
--- a/vmcore.h
+++ b/vm
core.h
@@ -416,9 +416,9 @@ struct rbunblockcallback {
struct rbmutexstruct;

#ifdef MINSIGSTKSZ
-#define ALTSTACKSIZE (MINSIGSTKSZ2)
+#define ALTSTACKSIZE (MINSIGSTKSZ
10)
#else
-#define ALTSTACKSIZE (41024)
+#define ALTSTACKSIZE (20
1024)
#endif

typedef struct rbthreadstruct {

以下のチケットも参考にしました。
http://bugs.ruby-lang.org/issues/5139


Related issues

Related to ruby-trunk - Bug #953: 深い入れ子の配列の取り扱いで落ちる Closed 12/30/2008
Related to ruby-trunk - Bug #5139: sigsegv のスタックオーバフロー Closed 08/02/2011
Related to ruby-trunk - Feature #7095: Non-recursive marking Closed 10/02/2012
Related to ruby-trunk - Bug #6058: Stack overflow in SEGV Handler Closed 02/22/2012

Associated revisions

Revision 38409
Added by Motohiro KOSAKI over 1 year ago

  • signal.c (rbsigaltstacksize): new. calculate stack size for sigsegv handler. enlarge value when x86 or x86_64 on Linux. Linux has very small MINSIGSTKSZ size (2048 bytes) and our sigsegv routine need 5KiB at least. [Bug #7141]
  • internal.h: add declaration of rbsigaltstacksize().
  • vmcore.h: remove ALTSTACK_SIZE definition.

  • signal.c (rbregistersigaltstack): replace ALTSTACKSIZE with
    rbsigaltstacksize();

  • gc.c (Init_heap): ditto.

  • vm.c (th_init): ditto.

History

#1 Updated by Motohiro KOSAKI over 1 year ago

  • Status changed from Open to Assigned
  • Assignee set to Motohiro KOSAKI

あー、では私のミスっぽいのでちょっと預からさせてくださいませ

#2 Updated by Motohiro KOSAKI over 1 year ago

うちではこんな感じになりました。

setarch x86_64 -R ./miniruby -e 'Process.kill :SEGV, $$'


altstack: 0x7ffff7d83000-0x7ffff7d84000...0x7ffff7d85000-0x7ffff7d86000

                                           $RSP                             stack_usage   stack_usage_total

#0 BSDvfprintf 0x7ffff7d83e60 1520
#2 control
framedump 0x7ffff7d84450 320 1840
#3 rb
vmdebugstackdumpraw 0x7ffff7d84590 288 2128
#4 rb
vmbugreport 0x7ffff7d846b0 328 2456
#5 report
bug 0x7ffff7d84820 336 2792
#6 rb_bug 0x7ffff7d84970 272 3064
#7 sigsegv 0x7ffff7d84a80

原因は BSDvfprintfが char buf[1024] とやっているのが主犯のようですが、これはfloatを使わないときは
68でOKみたいなんで、float使う時だけallocaで増やすのがよさそう。
2.0では時間がないので単純に ALT
STACK_SIZE を増やすので逃げられそう。いまが4096なんで8192まで
増やせばまず大丈夫。

それはそれとして、シグナルハンドラからfprintf呼んでるのはデッドロックの危険があるので、
snprintf + writeに書き換えないとダメじゃないかな。将来的には
(rubyは自前のsnprintfを持ってるのでthread safeかつasync signal safeで動きそう)

#3 Updated by Motohiro KOSAKI over 1 year ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r38409.
Narihiro, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • signal.c (rbsigaltstacksize): new. calculate stack size for sigsegv handler. enlarge value when x86 or x86_64 on Linux. Linux has very small MINSIGSTKSZ size (2048 bytes) and our sigsegv routine need 5KiB at least. [Bug #7141]
  • internal.h: add declaration of rbsigaltstacksize().
  • vmcore.h: remove ALTSTACK_SIZE definition.

  • signal.c (rbregistersigaltstack): replace ALTSTACKSIZE with
    rbsigaltstacksize();

  • gc.c (Init_heap): ditto.

  • vm.c (th_init): ditto.

#4 Updated by Motohiro KOSAKI over 1 year ago

こういうのも取れました。ようするにglibcのvfprintfもruby内蔵のvfprintfと同じぐらいスタックを使うので(おそらく理由も同じ)
まったく同じことが起きているようです。
これは、そもそもfprintfを使っているのが間違いなので、全部書き換えるべきなんですが、2.0では単純にスタックサイズを増やして
逃げます。

altstack: 0x7ffff7d92000-0x7ffff7d93000...0x7ffff7d94000-0x7ffff7d95000

                                     $rsp                      stack_usage

#0 IOvfprintfinternal 0x7ffff7d92f80 1728
#1 _
fprintfchk 0x7ffff7d93640 240
#2 fprintf 0x7ffff7d93730 0
#3 rbvmdebugstackdumpraw 0x7ffff7d93730
#4 rbvmbugreport
#5 reportbug
#6 rb
bug
#7 sigsegv
#8

Also available in: Atom PDF