Bug #8254

Ruby segfaults on second SystemStackError from parser

Added by Charlie Somerville about 1 year ago. Updated about 1 year ago.

[ruby-core:54175]
Status:Closed
Priority:Normal
Assignee:-
Category:-
Target version:-
ruby -v:ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin11.4.0] Backport:

Description

=begin
When the parser overflows the stack, it raises SystemStackError.

The second time this happens, Ruby segfaults.

Code sample:

n = 10_000 # adjust for your platform
begin
  eval "1+" * n + "1"
rescue SystemStackError
  eval "1+" * n + "1"
end

=end

Associated revisions

Revision 40402
Added by Charlie Somerville about 1 year ago

  • configure.in: Use sigsetjmp by default so jumping out of signal
    handlers properly restores the signal mask and SS_ONSTACK flag.
    [Bug #8254]

  • configure.in: Manually check for presence of sigsetjmp. It is not a
    function on some systems, so ACCHECKFUNCS cannot be used.

History

#1 Updated by Charlie Somerville about 1 year ago

=begin
It seems to happen with any stack overflow from C:

#include <ruby/ruby.h>

VALUE f() {
    f();
}

Init_x() {
    rb_define_global_function("f", f, 0);
}

When (({f})) is called the second time, Ruby segfaults.
=end

#2 Updated by Charlie Somerville about 1 year ago

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

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


  • configure.in: Use sigsetjmp by default so jumping out of signal
    handlers properly restores the signal mask and SS_ONSTACK flag.
    [Bug #8254]

  • configure.in: Manually check for presence of sigsetjmp. It is not a
    function on some systems, so ACCHECKFUNCS cannot be used.

#3 Updated by Koichi Sasada about 1 year ago

charliesome discovered that the reason of this issues is longjmp from
segv handler.

After receiving SIGSEGV, segv handler runs on the altstack.
And returns by longjmp ruby's world if it is caused by stack overflow.
https://github.com/ruby/ruby/blob/trunk/signal.c#L670
"longjmp" doesn't care about signal status, and system can't restore
signal status (especially altstack status). System assumes that altstack
is used continuously. and second sigsegv handler can't use altstack.

To solve this issue, charliesome replaced all of setjmp/longjmp pair to
sigsetjmp/siglongjmp by r40402. This change fixes this problem.

However, sigsetjmp/siglongjmp (especially sigsetjmp) requires system
calls and slower than setjmp on the older systems.

$ time ./miniruby -ve '5000000.times{1.times{}}'
ruby 2.1.0dev (2013-04-21 trunk 40402) [x86_64-linux]
real 0m3.393s
user 0m1.904s
sys 0m1.488s

$ time ../versions/install-trunk-daily2013-04-16T1200/bin/ruby -ve
'5000000.times{1.times{}}'
ruby 2.1.0dev (2013-04-16 trunk 40318) [x86_64-linux]
real 0m1.221s
user 0m1.216s
sys 0m0.008s

on Linux 2.6.32-5-amd64 (Debian squid)

So I asked to revert this change (r40403).


charliesome proposed several solutions:

(1) Use libsigsegv

  • I'm not sure because it seems GNU product.

    (2) Use sigsetjmp/siglongjmp on newer systems only if there is no
    performance problem

    (3) Mix sigsetjmp/siglongjmp and setjmp/longjmp

    We need to restore signal status (altstack status), so use siglongjmp
    only at segv handler.

    (S1) [main] sigsetjmp(root) ($)
    (S2) [main] sigsetjmp(root) .... [foo] setjmp(foo) ... ($)
    (S3) [main] sigsetjmp(root) .... [foo] setjmp(foo) ...
    [bar] setjmp(bar) ($)
    (S4) [main] sigsetjmp(root) .... [foo] setjmp(foo) ...
    [bar] setjmp(bar) ... [SEGV handler] siglongjmp(root) ($)
    (S5) [main] sigsetjmp(root) ($) # signal status was restored
    (S6) [main] sigsetjmp(root) longjmp(bar) ($)
    (S7) [main] sigsetjmp(root) .... [foo] setjmp(foo) ...
    [bar] setjmp(bar) ($)

    ($) is program counter.

    • This is interesting technique, but I'm not sure this approach works fine because longjmp() at (S6) jumps into deeper stack frame.

    Ideas are welcome.

    (2013/04/11 21:36), charliesome (Charlie Somerville) wrote:

    Issue #8254 has been updated by charliesome (Charlie Somerville).

    =begin
    It seems to happen with any stack overflow from C:

    #include <ruby/ruby.h>
    
    VALUE f() {
        f();
    }
    
    Init_x() {
        rb_define_global_function("f", f, 0);
    }
    

    When (({f})) is called the second time, Ruby segfaults.

    =end

    Bug #8254: Ruby segfaults on second SystemStackError from parser
    https://bugs.ruby-lang.org/issues/8254#change-38446

    Author: charliesome (Charlie Somerville)
    Status: Open
    Priority: Normal
    Assignee:
    Category:
    Target version:
    ruby -v: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin11.4.0]

    =begin
    When the parser overflows the stack, it raises SystemStackError.

    The second time this happens, Ruby segfaults.

    Code sample:

    n = 10_000 # adjust for your platform
    begin
      eval "1+" * n + "1"
    rescue SystemStackError
      eval "1+" * n + "1"
    end
    

    =end

    // SASADA Koichi at atdot dot net

Also available in: Atom PDF