Bug #5801

Enumerable#take_while の proc を外に出して使うと Segv

Added by satoshi shiba over 2 years ago. Updated about 2 years ago.

[ruby-dev:45040]
Status:Closed
Priority:Normal
Assignee:-
Category:-
Target version:1.9.3
ruby -v:ruby 1.9.3p6 (2011-12-20 revision 34080) [i686-linux] Backport:

Description

芝と申します。

下の再現コードのように、Enumerable#take_while の Proc を
外に出して使うと Segv が発生します。

再現コード

=begin
class A
include Enumerable
def each(&b)
$block = b
yield
end
end
puts A.new.take_while{true}
100.times{$block.call}
=end

$block には、takewhile 内で使用する takewhilei (enum.c)
が C の Proc として入ります。この Proc を上の例のように
グローバル変数などに入れて、take
while の呼出し後に使用
すると Segv を吐きます。

原因はおそらく、enumtakewhile と takewhilei で、
take_while の返り値である配列オブジェクトをポインタ経由で
渡していることではないでしょうか。

/* enumtakewhile と takewhilei /
static VALUE
enumtakewhile(VALUE obj)
{
VALUE ary;
RETURNENUMERATOR(obj, 0, 0);
ary = rb
arynew();
rb
blockcall(obj, ideach, 0, 0, takewhilei, (VALUE)&ary /
<= ココ */);
return ary;
}

static VALUE
takewhilei(VALUE i, VALUE ary / <= ココ /, int argc, VALUE *argv)
{
if (!RTEST(enumyield(argc, argv))) rbiterbreak();
rb
ary_push(
ary /* <= ココ */, enumvaluespack(argc, argv));
return Qnil;
}

enumtakewhile では rbblockcall を呼び出すときに &ary を
引数として渡していますが、ary は C のローカル変数であるため、
C のスタックを指すことになります。このため、takewhile の
呼出し後にグローバル変数などに保持された Proc から take
while_i
が呼び出されると、*ary の値が書き換わってしまっていて Segv を
吐いているように見えます。

以下のパッチでこのバグが再現しないことは確認できました。
参考にしていただければ幸いです。
よろしくお願いいたします。

/* パッチ */

Index: enum.c

--- enum.c (revision 34107)
+++ enum.c (working copy)
@@ -2053,7 +2053,7 @@
takewhilei(VALUE i, VALUE ary, int argc, VALUE *argv)
{
if (!RTEST(enumyield(argc, argv))) rbiterbreak();
- rb
ary_push(
ary, enumvaluespack(argc, argv));
+ rbarypush((VALUE)ary, enumvaluespack(argc, argv));
return Qnil;
}

@@ -2079,7 +2079,7 @@

 RETURN_ENUMERATOR(obj, 0, 0);
 ary = rb_ary_new();
  • rbblockcall(obj, ideach, 0, 0, takewhile_i, (VALUE)&ary);
  • rbblockcall(obj, ideach, 0, 0, takewhile_i, ary); return ary; }

Associated revisions

Revision 34661
Added by Nobuyoshi Nakada about 2 years ago

  • enum.c: move work variables to objects not to let called blocks access stack area out of scope. [Bug #5801]

History

#1 Updated by satoshi shiba over 2 years ago

Enumerable#grep なども同様の問題がありますね。
以下の再現コードで Segv を吐きます。

enum.c で、XX_i という形の関数の第二引数が C の
スタックへのポインタになってるやつは、全部同じ問題を
含んでいるのではないでしょうか。

再現コード

=begin
class A
include Enumerable
def each(&b)
$block = b
yield
end
end
puts A.new.grep(//)
100.times{$block.call}
=end

static VALUE
grepi(VALUE i, VALUE args, int argc, VALUE argv)
{
VALUE *arg = (VALUE *)args; /
arg は C のスタックを指す */
ENUM
WANT_SVALUE();

if (RTEST(rb_funcall(arg[0], id_eqq, 1, i))) {
    rb_ary_push(arg[1], i);
}
return Qnil;

}

#2 Updated by Nobuyoshi Nakada about 2 years ago

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

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


  • enum.c: move work variables to objects not to let called blocks access stack area out of scope. [Bug #5801]

Also available in: Atom PDF