Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DoS: Infinite loop in parser when ruby script contains an unclosed heredoc named with empty string #5676

Closed
lopopolo opened this issue Mar 24, 2022 · 6 comments

Comments

@lopopolo
Copy link
Contributor

lopopolo commented Mar 24, 2022

Reproduction

MRI

$ ruby -v
ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [x86_64-darwin21]
$ ruby -e "+<<''&"
-e:1: can't find string "" anywhere before EOF
-e:1: syntax error, unexpected end-of-input
+<<''&
$ ruby -e "<<'DOC'&"
-e:1: can't find string "DOC" anywhere before EOF
-e:1: syntax error, unexpected end-of-input
<<'DOC'&

mruby

$ /usr/local/var/rbenv/versions/mruby-3.0.0/bin/mruby -v
mruby 3.0.0 (2021-03-05)
$ /usr/local/var/rbenv/versions/mruby-3.0.0/bin/mruby -e "+<<''&"
-e:3:0: syntax error, unexpected tHEREDOC_END
^C
$ /usr/local/var/rbenv/versions/mruby-3.0.0/bin/mruby -e "<<'DOC'&"
-e:3:0: can't find heredoc delimiter "DOC" anywhere before EOF
-e:3:0: syntax error, unexpected $end
@lopopolo
Copy link
Contributor Author

I found this bug when fuzzing artichoke with rust-fuzz and libFuzzer:

https://github.com/artichoke/artichoke/runs/5675904707?check_suite_focus=true

SUMMARY: libFuzzer: timeout

────────────────────────────────────────────────────────────────────────────────

Failing input:

	fuzz/artifacts/eval/timeout-db7f3f26769301b82010854d3089f8d64f24740e

Output of `std::fmt::Debug`:

	[43, 60, 60, 39, 39, 38]

Reproduce with:

	cargo fuzz run eval fuzz/artifacts/eval/timeout-db7f3f26769301b82010854d3089f8d64f24740e

Minimize test case with:

	cargo fuzz tmin eval fuzz/artifacts/eval/timeout-db7f3f26769301b82010854d3089f8d64f24740e

────────────────────────────────────────────────────────────────────────────────

printing the bytes that trigger the infinite loop:

$ irb
[3.1.1] > [43, 60, 60, 39, 39, 38].map(&:chr).join
=> "+<<''&"

@lopopolo lopopolo changed the title Infinite loop when ruby script contains an unclosed heredoc named with empty string DoS: Infinite loop in parser when ruby script contains an unclosed heredoc named with empty string Mar 24, 2022
@lopopolo
Copy link
Contributor Author

Here's a couple of stack traces I pulled from LLDB:

* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00000001003ad43d artichoke`tokadd(p=0x0000000102015420, c=10) at parse.y:4398:17
    frame #1: 0x00000001003aaeb4 artichoke`parse_string(p=0x0000000102015420) at parse.y:4747:7
    frame #2: 0x00000001003a59b1 artichoke`parser_yylex(p=0x0000000102015420) at parse.y:5135:16
    frame #3: 0x00000001003a2e28 artichoke`yylex(lval=0x00007ff7bfefd178, p=0x0000000102015420) at parse.y:6463:10
    frame #4: 0x000000010039954b artichoke`yyparse(p=0x0000000102015420) at y.tab.c:6235:16
    frame #5: 0x0000000100398eff artichoke`mrb_parser_parse(p=0x0000000102015420, c=0x00006000021040a0) at parse.y:6528:11
    frame #6: 0x00000001003a22e6 artichoke`mrb_parse_nstring(mrb=0x000000010200c200, s="+<<''&", len=6, c=0x00006000021040a0) at parse.y:6741:3
    frame #7: 0x00000001003a2bc1 artichoke`mrb_load_nstring_cxt(mrb=0x000000010200c200, s="+<<''&", len=6, c=0x00006000021040a0) at parse.y:6879:29
    frame #8: 0x00000001002bcf59 artichoke`_$LT$artichoke_backend..sys..protect..Eval$u20$as$u20$artichoke_backend..sys..protect..Protect$GT$::run::h51fe37d296920b5d(mrb=0x000000010200c200, data=mrb_value @ 0x00007ff7bfefdc38) at protect.rs:115:9
    frame #9: 0x00000001003dc93a artichoke`mrb_protect(mrb=0x000000010200c200, body=(artichoke`_$LT$artichoke_backend..sys..protect..Eval$u20$as$u20$artichoke_backend..sys..protect..Protect$GT$::run::h51fe37d296920b5d at protect.rs:105), data=mrb_value @ scalar, state="") at exception.c:17:14
    frame #10: 0x000000010027eea9 artichoke`artichoke_backend::sys::protect::protect::h174159d37e65b44b(mrb=0x000000010200c200, data=<unavailable>) at protect.rs:46:17
    frame #11: 0x00000001002bca4c artichoke`artichoke_backend::sys::protect::eval::hf783a80bec167b2c(mrb=0x000000010200c200, context=0x00006000021040a0, code=size=6) at protect.rs:25:5
    frame #12: 0x0000000100188771 artichoke`artichoke_backend::eval::_$LT$impl$u20$artichoke_core..eval..Eval$u20$for$u20$artichoke_backend..artichoke..Artichoke$GT$::eval::_$u7b$$u7b$closure$u7d$$u7d$::ha8c6d701e4b26d43(mrb=0x000000010200c200) at eval.rs:26:42
    frame #13: 0x000000010028cb94 artichoke`artichoke_backend::artichoke::Artichoke::with_ffi_boundary::h685c1d84e27513b1(self=0x00007ff7bfefee68, func={closure#0} @ 0x00007ff7bfefe1f0) at artichoke.rs:99:26
    frame #14: 0x0000000100337822 artichoke`artichoke_backend::eval::_$LT$impl$u20$artichoke_core..eval..Eval$u20$for$u20$artichoke_backend..artichoke..Artichoke$GT$::eval::h3d4d991b4eea6846(self=0x00007ff7bfefee68, code=size=6) at eval.rs:26:13
    frame #15: 0x0000000100337d4b artichoke`artichoke_backend::eval::_$LT$impl$u20$artichoke_core..eval..Eval$u20$for$u20$artichoke_backend..artichoke..Artichoke$GT$::eval_os_str::h2718fcd4fb5ef712(self=0x00007ff7bfefee68, code=0x0000600000008100) at eval.rs:51:9
    frame #16: 0x000000010000af85 artichoke`artichoke::ruby::execute_inline_eval::h0360a6094a00e12d(interp=0x00007ff7bfefee68, error=0x00007ff7bfeff1a0, commands=size=1, fixture=Option<&std::path::Path> @ 0x00007ff7bfefe5f8) at ruby.rs:180:27
    frame #17: 0x00000001000098e9 artichoke`artichoke::ruby::entrypoint::hcdff3bfb35549e5e(interp=0x00007ff7bfefee68, args=Args @ 0x00007ff7bfefeec0, input=Stdin @ 0x00007ff7bfefea88, error=0x00007ff7bfeff1a0) at ruby.rs:134:9
    frame #18: 0x000000010000b562 artichoke`artichoke::ruby::run::h314d0ceff9c2a328(args=Args @ 0x00007ff7bfeff1f8, input=Stdin @ 0x00007ff7bfefef30, error=0x00007ff7bfeff1a0) at ruby.rs:97:18
    frame #19: 0x000000010000413b artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:68:11
    frame #20: 0x0000000100013d6e artichoke`core::ops::function::FnOnce::call_once::hb1ed9a9e31a03189((null)=(artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:56), (null)=<unavailable>) at function.rs:227:5
    frame #21: 0x00000001000177e1 artichoke`std::sys_common::backtrace::__rust_begin_short_backtrace::hac36f7b49d521f3d(f=(artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:56)) at backtrace.rs:123:18
    frame #22: 0x0000000100002c14 artichoke`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h53d8df2a40158861 at rt.rs:145:18
    frame #23: 0x000000010068b74f artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::h366f85254a0aedbd at function.rs:259:13 [opt]
    frame #24: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::do_call::hcd7d6ba18808215d at panicking.rs:406:40 [opt]
    frame #25: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::h8b7328b76a891d08 at panicking.rs:370:19 [opt]
    frame #26: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panic::catch_unwind::h63bbd5ef51caa433 at panic.rs:133:14 [opt]
    frame #27: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h2efd4ac38212ab36 at rt.rs:128:48 [opt]
    frame #28: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::do_call::hfdf1ca9f7283bb6b at panicking.rs:406:40 [opt]
    frame #29: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::h5e8a1aa20c127c08 at panicking.rs:370:19 [opt]
    frame #30: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panic::catch_unwind::he9efa865311db8e6 at panic.rs:133:14 [opt]
    frame #31: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 at rt.rs:128:20 [opt]
    frame #32: 0x0000000100002bee artichoke`std::rt::lang_start::hcf88ede926ea5b78(main=(artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:56), argc=3, argv=0x00007ff7bfeff570) at rt.rs:144:17
    frame #33: 0x0000000100005416 artichoke`main + 22
    frame #34: 0x000000010131151e dyld`start + 462
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00000001003ac2c4 artichoke`nextc(p=0x0000000102015420) at parse.y:4220:8
    frame #1: 0x00000001003aae2a artichoke`parse_string(p=0x0000000102015420) at parse.y:4743:15
    frame #2: 0x00000001003a59b1 artichoke`parser_yylex(p=0x0000000102015420) at parse.y:5135:16
    frame #3: 0x00000001003a2e28 artichoke`yylex(lval=0x00007ff7bfefd178, p=0x0000000102015420) at parse.y:6463:10
    frame #4: 0x000000010039954b artichoke`yyparse(p=0x0000000102015420) at y.tab.c:6235:16
    frame #5: 0x0000000100398eff artichoke`mrb_parser_parse(p=0x0000000102015420, c=0x00006000021040a0) at parse.y:6528:11
    frame #6: 0x00000001003a22e6 artichoke`mrb_parse_nstring(mrb=0x000000010200c200, s="+<<''&", len=6, c=0x00006000021040a0) at parse.y:6741:3
    frame #7: 0x00000001003a2bc1 artichoke`mrb_load_nstring_cxt(mrb=0x000000010200c200, s="+<<''&", len=6, c=0x00006000021040a0) at parse.y:6879:29
    frame #8: 0x00000001002bcf59 artichoke`_$LT$artichoke_backend..sys..protect..Eval$u20$as$u20$artichoke_backend..sys..protect..Protect$GT$::run::h51fe37d296920b5d(mrb=0x000000010200c200, data=mrb_value @ 0x00007ff7bfefdc38) at protect.rs:115:9
    frame #9: 0x00000001003dc93a artichoke`mrb_protect(mrb=0x000000010200c200, body=(artichoke`_$LT$artichoke_backend..sys..protect..Eval$u20$as$u20$artichoke_backend..sys..protect..Protect$GT$::run::h51fe37d296920b5d at protect.rs:105), data=mrb_value @ 0x00007ff7bfefddb0, state="") at exception.c:17:14
    frame #10: 0x000000010027eea9 artichoke`artichoke_backend::sys::protect::protect::h174159d37e65b44b(mrb=0x000000010200c200, data=<unavailable>) at protect.rs:46:17
    frame #11: 0x00000001002bca4c artichoke`artichoke_backend::sys::protect::eval::hf783a80bec167b2c(mrb=0x000000010200c200, context=0x00006000021040a0, code=size=6) at protect.rs:25:5
    frame #12: 0x0000000100188771 artichoke`artichoke_backend::eval::_$LT$impl$u20$artichoke_core..eval..Eval$u20$for$u20$artichoke_backend..artichoke..Artichoke$GT$::eval::_$u7b$$u7b$closure$u7d$$u7d$::ha8c6d701e4b26d43(mrb=0x000000010200c200) at eval.rs:26:42
    frame #13: 0x000000010028cb94 artichoke`artichoke_backend::artichoke::Artichoke::with_ffi_boundary::h685c1d84e27513b1(self=0x00007ff7bfefee68, func={closure#0} @ 0x00007ff7bfefe1f0) at artichoke.rs:99:26
    frame #14: 0x0000000100337822 artichoke`artichoke_backend::eval::_$LT$impl$u20$artichoke_core..eval..Eval$u20$for$u20$artichoke_backend..artichoke..Artichoke$GT$::eval::h3d4d991b4eea6846(self=0x00007ff7bfefee68, code=size=6) at eval.rs:26:13
    frame #15: 0x0000000100337d4b artichoke`artichoke_backend::eval::_$LT$impl$u20$artichoke_core..eval..Eval$u20$for$u20$artichoke_backend..artichoke..Artichoke$GT$::eval_os_str::h2718fcd4fb5ef712(self=0x00007ff7bfefee68, code=0x0000600000008100) at eval.rs:51:9
    frame #16: 0x000000010000af85 artichoke`artichoke::ruby::execute_inline_eval::h0360a6094a00e12d(interp=0x00007ff7bfefee68, error=0x00007ff7bfeff1a0, commands=size=1, fixture=Option<&std::path::Path> @ 0x00007ff7bfefe5f8) at ruby.rs:180:27
    frame #17: 0x00000001000098e9 artichoke`artichoke::ruby::entrypoint::hcdff3bfb35549e5e(interp=0x00007ff7bfefee68, args=Args @ 0x00007ff7bfefeec0, input=Stdin @ 0x00007ff7bfefea88, error=0x00007ff7bfeff1a0) at ruby.rs:134:9
    frame #18: 0x000000010000b562 artichoke`artichoke::ruby::run::h314d0ceff9c2a328(args=Args @ 0x00007ff7bfeff1f8, input=Stdin @ 0x00007ff7bfefef30, error=0x00007ff7bfeff1a0) at ruby.rs:97:18
    frame #19: 0x000000010000413b artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:68:11
    frame #20: 0x0000000100013d6e artichoke`core::ops::function::FnOnce::call_once::hb1ed9a9e31a03189((null)=(artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:56), (null)=<unavailable>) at function.rs:227:5
    frame #21: 0x00000001000177e1 artichoke`std::sys_common::backtrace::__rust_begin_short_backtrace::hac36f7b49d521f3d(f=(artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:56)) at backtrace.rs:123:18
    frame #22: 0x0000000100002c14 artichoke`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h53d8df2a40158861 at rt.rs:145:18
    frame #23: 0x000000010068b74f artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::h366f85254a0aedbd at function.rs:259:13 [opt]
    frame #24: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::do_call::hcd7d6ba18808215d at panicking.rs:406:40 [opt]
    frame #25: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::h8b7328b76a891d08 at panicking.rs:370:19 [opt]
    frame #26: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panic::catch_unwind::h63bbd5ef51caa433 at panic.rs:133:14 [opt]
    frame #27: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h2efd4ac38212ab36 at rt.rs:128:48 [opt]
    frame #28: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::do_call::hfdf1ca9f7283bb6b at panicking.rs:406:40 [opt]
    frame #29: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panicking::try::h5e8a1aa20c127c08 at panicking.rs:370:19 [opt]
    frame #30: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 [inlined] std::panic::catch_unwind::he9efa865311db8e6 at panic.rs:133:14 [opt]
    frame #31: 0x000000010068b748 artichoke`std::rt::lang_start_internal::hb74872162e3d56c9 at rt.rs:128:20 [opt]
    frame #32: 0x0000000100002bee artichoke`std::rt::lang_start::hcf88ede926ea5b78(main=(artichoke`artichoke::main::hc27bd9053c728619 at artichoke.rs:56), argc=3, argv=0x00007ff7bfeff570) at rt.rs:144:17
    frame #33: 0x0000000100005416 artichoke`main + 22
    frame #34: 0x000000010131151e dyld`start + 462

@lopopolo
Copy link
Contributor Author

This also reproduces on 3.1.0-rc. Here's a stack I pulled during the infinite loop:

* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x0000000100085557 mruby`parse_string(p=0x000000010100d620) at parse.y:4852:60 [opt]
   4849             --len;
   4850           }
   4851         }
-> 4852         if ((len-1 == hinf->term_len) && (strncmp(s, hinf->term, len-1) == 0)) {
   4853           heredoc_remove_indent(p, hinf);
   4854           return tHEREDOC_END;
   4855         }
Target 0: (mruby) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x0000000100085557 mruby`parse_string(p=0x000000010100d620) at parse.y:4852:60 [opt]
    frame #1: 0x000000010007a685 mruby`parser_yylex(p=<unavailable>) at parse.y:0:11 [opt] [artificial]
    frame #2: 0x000000010005ede1 mruby`yyparse [inlined] yylex(lval=<unavailable>, p=0x000000010100d620) at parse.y:6549:10 [opt]
    frame #3: 0x000000010005edb7 mruby`yyparse(p=0x000000010100d620) at y.tab.c:6067:16 [opt]
    frame #4: 0x000000010005ea89 mruby`mrb_parser_parse(p=0x000000010100d620, c=<unavailable>) at parse.y:6615:9 [opt]
    frame #5: 0x00000001000784c2 mruby`mrb_load_string_cxt [inlined] mrb_parse_nstring(mrb=0x0000000101008200, s="+<<''&", len=<unavailable>) at parse.y:6815:3 [opt]
    frame #6: 0x0000000100078433 mruby`mrb_load_string_cxt [inlined] mrb_load_nstring_cxt(mrb=0x0000000101008200, s="+<<''&", len=<unavailable>) at parse.y:6953:29 [opt]
    frame #7: 0x0000000100078433 mruby`mrb_load_string_cxt(mrb=0x0000000101008200, s="+<<''&", c=0x0000600002104000) at parse.y:6965:10 [opt]
    frame #8: 0x0000000100000f8f mruby`main(argc=<unavailable>, argv=<unavailable>) at mruby.c:352:11 [opt]
    frame #9: 0x000000010016951e dyld`start + 462

@matz matz closed this as completed in b4168c9 Mar 24, 2022
@matz
Copy link
Member

matz commented Mar 24, 2022

Thank you! We forgot to check empty here-doc delimiters.

@lopopolo
Copy link
Contributor Author

Thanks @matz. Would you be willing to backport this fix to the pending 3.1 release?

@matz
Copy link
Member

matz commented Mar 25, 2022

Ah, I will ask the release manager.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants