Skip to content

Commit

Permalink
[Bug #20453] segfault in Regexp timeout
Browse files Browse the repository at this point in the history
https://bugs.ruby-lang.org/issues/20228 started freeing `stk_base` to
avoid a memory leak. But `stk_base` is sometimes stack allocated (using
`xalloca`), so the free only works if the regex stack has grown enough
to hit `stack_double` (which uses `xmalloc` and `xrealloc`).

To reproduce the problem on master and 3.3.1:

```ruby
Regexp.timeout = 0.001
/^(a*)x$/ =~ "a" * 1000000 + "x"'
```

Some details about this potential fix:

`stk_base == stk_alloc` on
[init](https://github.com/ruby/ruby/blob/dde99215f2bc60c22a00fc941ff7f714f011e920/regexec.c#L1153),
so if `stk_base != stk_alloc` we can be sure we called
[`stack_double`](https://github.com/ruby/ruby/blob/dde99215f2bc60c22a00fc941ff7f714f011e920/regexec.c#L1210)
and it's safe to free. It's also safe to free if we've
[saved](https://github.com/ruby/ruby/blob/dde99215f2bc60c22a00fc941ff7f714f011e920/regexec.c#L1187-L1189)
the stack to `msa->stack_p`, since we do the `stk_base != stk_alloc`
check before saving.

This matches the check we do inside
[`stack_double`](https://github.com/ruby/ruby/blob/dde99215f2bc60c22a00fc941ff7f714f011e920/regexec.c#L1221)
  • Loading branch information
composerinteralia authored and peterzhu2118 committed Apr 25, 2024
1 parent 7ab1a60 commit d292a9b
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 1 deletion.
3 changes: 2 additions & 1 deletion regexec.c
Expand Up @@ -4218,7 +4218,8 @@ match_at(regex_t* reg, const UChar* str, const UChar* end,

timeout:
xfree(xmalloc_base);
xfree(stk_base);
if (stk_base != stk_alloc || IS_NOT_NULL(msa->stack_p))
xfree(stk_base);
HANDLE_REG_TIMEOUT_IN_MATCH_AT;
}

Expand Down
11 changes: 11 additions & 0 deletions test/ruby/test_regexp.rb
Expand Up @@ -1827,6 +1827,17 @@ def test_s_timeout_memory_leak
end;
end

def test_bug_20453
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
begin;
Regexp.timeout = 0.001
assert_raise(Regexp::TimeoutError) do
/^(a*)x$/ =~ "a" * 1000000 + "x"
end
end;
end

def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect }
Expand Down

0 comments on commit d292a9b

Please sign in to comment.