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

Segfault (stack overflow?) while fuzzing Perl 5.21.8 #14421

Closed
p5pRT opened this issue Jan 17, 2015 · 12 comments
Labels

Comments

@p5pRT
Copy link
Collaborator

@p5pRT p5pRT commented Jan 17, 2015

Migrated from rt.perl.org#123617 (status was 'resolved')

Searchable as RT123617$

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jan 17, 2015

From @geeknik

Good afternoon, I'm still fuzzing Perl 5.21.8, built from git source on 14
January 2015. I used the afl-gcc compiler (http​://lcamtuf.coredump.cx/afl/)
to compile.

CC=/path/to/afl-gcc ./Configure

I did not use AFL_HARDEN=1 when I ran make, and I also compiled a second
binary with no stack protection to try and get a little better insight into
this issue. I've attached the core dump and the test case which causes this
seg fault.

geeknik@​deb7fuzz​:~/findings/perl5/fuzzer03/crashes$
/home/geeknik/perl5-no-stack-protect/perl
id\​:000045\,sig\​:11\,src\​:009416+026249\,op\​:splice\,rep\​:1

Bareword found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 13, near "print
"into"
  (Might be a runaway multi-line "" string starting on line 12)
(Do you need to predeclare print?)
Backslash found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 20, near
"foreachme\"
String found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 20, near "print ""
(Missing semicolon on previous line?)
Bareword found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 21, near "print
"otform"
  (Might be a runaway multi-line "" string starting on line 20)
(Do you need to predeclare print?)
Backslash found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 21, near "<P>\"
(Missing operator before \?)
String found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 21, near "print ""
(Missing semicolon on previous line?)
String found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 21, near "print ""
(Missing semicolon on previous line?)
Backslash found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 23, near "is\"
  (Might be a runaway multi-line "" string starting on line 21)
String found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 23, near "print ""
(Missing semicolon on previous line?)
Bareword found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 24, near "print
"necessary"
  (Might be a runaway multi-line "" string starting on line 23)
(Do you need to predeclare print?)
Backslash found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 26, near "m signs\"
(Do you need to predeclare m?)
String found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 26, near "print ""
(Missing semicolon on previous line?)
Bareword found where operator expected at
id​:000045,sig​:11,src​:009416+026249,op​:splice,rep​:1 line 27, near "print
"into"
  (Might be a runaway multi-line "" string starting on line 26)
(Do you need to predeclare print?)
Segmentation fault (core dumped)

geeknik@​deb7fuzz​:~/findings/perl5/fuzzer03/crashes$ valgrind -q
/home/geeknik/perl5-no-stack-protect/perl
id\​:000045\,sig\​:11\,src\​:009416+026249\,op\​:splice\,rep\​:1
==32809== Invalid read of size 4
==32809== at 0x433C8A​: S_aassign_common_vars (op.c​:6402)
==32809== by 0x47A776​: Perl_rpeep (op.c​:13955)
==32809== by 0x475B06​: Perl_newPROG (op.c​:4100)
==32809== by 0x5B7BFF​: Perl_yyparse (perly.y​:120)
==32809== by 0x4E63E4​: perl_parse (perl.c​:2273)
==32809== by 0x4299CB​: main (perlmain.c​:114)
==32809== Address 0x18 is not stack'd, malloc'd or (recently) free'd
==32809==
==32809==
==32809== Process terminating with default action of signal 11 (SIGSEGV)​:
dumping core
==32809== Access not within mapped region at address 0x18
==32809== at 0x433C8A​: S_aassign_common_vars (op.c​:6402)
==32809== by 0x47A776​: Perl_rpeep (op.c​:13955)
==32809== by 0x475B06​: Perl_newPROG (op.c​:4100)
==32809== by 0x5B7BFF​: Perl_yyparse (perly.y​:120)
==32809== by 0x4E63E4​: perl_parse (perl.c​:2273)
==32809== by 0x4299CB​: main (perlmain.c​:114)
==32809== If you believe this happened as a result of a stack
==32809== overflow in your program's main thread (unlikely but
==32809== possible), you can try to increase the size of the
==32809== main thread stack using the --main-stacksize= flag.
==32809== The main thread stack size used in this run was 8388608.
Segmentation fault

geeknik@​deb7fuzz​:~/findings/perl5/fuzzer03/crashes$ gdb
/home/geeknik/perl5-no-stack-protect/perl core
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see​:
<http​://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/geeknik/perl5-no-stack-protect/perl...done.
[New LWP 46646]

warning​: Can't read pathname for load map​: Input/output error.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `/home/geeknik/perl5-no-stack-protect/perl
id​:000045,sig​:11,src​:009416+026249,op'.
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000433c8a in S_aassign_common_vars (o=0xdfcc18) at op.c​:6402
6402 || (int)GvASSIGN_GENERATION(gv) == PL_generation)
(gdb) bt
#0 0x0000000000433c8a in S_aassign_common_vars (o=0xdfcc18) at op.c​:6402
#1 S_aassign_common_vars (o=0xdfca88) at op.c​:6420
#2 S_aassign_common_vars (o=<optimized out>) at op.c​:6420
#3 0x000000000047a777 in Perl_rpeep (o=0xdfca40) at op.c​:13955
#4 0x0000000000475b07 in Perl_newPROG (o=0xdfbb20) at op.c​:4100
#5 0x00000000005b7c00 in Perl_yyparse (gramtype=<optimized out>) at
perly.y​:120
#6 0x00000000004e63e5 in S_parse_body (xsinit=0x429dc0 <xs_init>, env=0x0)
at perl.c​:2273
#7 perl_parse (my_perl=<optimized out>, xsinit=0x429dc0 <xs_init>,
argc=<optimized out>, argv=<optimized out>, env=0x0)
  at perl.c​:1607
#8 0x00000000004299cc in main (argc=2, argv=0x7fffffffe338,
env=0x7fffffffe350) at perlmain.c​:114
#9 0x00007ffff6d7bead in __libc_start_main (main=<optimized out>,
argc=<optimized out>, ubp_av=<optimized out>,
  init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7fffffffe328) at libc-start.c​:244
#10 0x0000000000429ce5 in _start ()
(gdb) i r
rax 0xd 13
rbx 0xdfc6b0 14665392
rcx 0xd 13
rdx 0x0 0
rsi 0x540 1344
rdi 0x7 7
rbp 0xdfca88 0xdfca88
rsp 0x7fffffffde40 0x7fffffffde40
r8 0x4 4
r9 0x0 0
r10 0x65 101
r11 0x0 0
r12 0xdfcc18 14666776
r13 0xdfc1a0 14664096
r14 0x40 64
r15 0xffffffff 4294967295
rip 0x433c8a 0x433c8a <S_aassign_common_vars+4066>
eflags 0x10206 [ PF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb)

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jan 17, 2015

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jan 18, 2015

From @hvds

A perl built with debugging gives​:

% ./perl test.pl
perl​: toke.c​:2430​: S_sublex_done​: Assertion `(PL_parser->lex_inwhat) == OP_SUBST || (PL_parser->lex_inwhat) == OP_TRANS' failed.
Aborted (core dumped)
%

Here's a shorter testcase (which doesn't clarify much)​:

% cat test.pl
"$a{m/""$b
/ m ss
";@​c = split /x/
%

We seem to have a PL_lex_repl with PL_lex_inwhat == OP_MATCH, which clearly
can't happen.

Hugo

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jan 18, 2015

The RT System itself - Status changed from 'new' to 'open'

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jan 25, 2015

From @cpansprout

On Sat Jan 17 17​:09​:43 2015, hv wrote​:

A perl built with debugging gives​:

% ./perl test.pl
perl​: toke.c​:2430​: S_sublex_done​: Assertion `(PL_parser->lex_inwhat)
== OP_SUBST || (PL_parser->lex_inwhat) == OP_TRANS' failed.
Aborted (core dumped)
%

Here's a shorter testcase (which doesn't clarify much)​:

% cat test.pl
"$a{m/""$b
/ m ss
";@​c = split /x/
%

We seem to have a PL_lex_repl with PL_lex_inwhat == OP_MATCH, which
clearly
can't happen.

This is no doubt related to my two favourite japhs​:

s||${s/.*/|;
/s}Just another Perl hacker,
print

"${;s/.*/Just an";
other Perl hacker,
/s} die or return;
print

I don’t remember exactly how they work. At the time I wrote them I based them on the interesting implementation details I saw in toke.c, which had something to do with PL_sublex_info.sub_inwhat.

--

Father Chrysostomos

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jan 25, 2015

From @cpansprout

On Sat Jan 24 22​:08​:15 2015, sprout wrote​:

On Sat Jan 17 17​:09​:43 2015, hv wrote​:

A perl built with debugging gives​:

% ./perl test.pl
perl​: toke.c​:2430​: S_sublex_done​: Assertion `(PL_parser->lex_inwhat)
== OP_SUBST || (PL_parser->lex_inwhat) == OP_TRANS' failed.
Aborted (core dumped)
%

Here's a shorter testcase (which doesn't clarify much)​:

% cat test.pl
"$a{m/""$b
/ m ss
";@​c = split /x/
%

We seem to have a PL_lex_repl with PL_lex_inwhat == OP_MATCH, which
clearly
can't happen.

This is no doubt related to my two favourite japhs​:

s||${s/.*/|;
/s}Just another Perl hacker,
print

"${;s/.*/Just an";
other Perl hacker,
/s} die or return;
print

I don’t remember exactly how they work. At the time I wrote them I
based them on the interesting implementation details I saw in toke.c,
which had something to do with PL_sublex_info.sub_inwhat.

skipspace() in toke.c, which is usually used to skip whitespace between tokens, reads more input from the file if it needs to. But it checks first to see whether we are in a multiline construct and does not read more input if we are.

scan_str, which looks for the final delimiter of the string, does not check whether we are in a multiline construct, but just reads straight ahead.

So in this japh​:

s||${s/.*/|;
/s}Just another Perl hacker,
print

When the replacement part of the outer s||| is parsed, ‘${s/.*/’ is the current ‘line’ of input, and scan_str, trying to find the final delimiter for the s///, reads the next line of input from the stream, which is ‘/s}Just another Perl hacker,’. It finds its delimiter (and the /s flag to boot) immediately. Then the inner s/// construct is exited via S_sublex_done, which sets PL_sublex_info.sub_inwhat to 0 indiscriminately, even though we are still inside the outer string-like construct.

PL_sublex_info.sub_inwhat is what skipspace checks before reading more input.

That’s as far as my investigation has reached. I may not continue for another week.

--

Father Chrysostomos

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Feb 1, 2015

From @cpansprout

On Sat Jan 24 22​:44​:36 2015, sprout wrote​:

On Sat Jan 24 22​:08​:15 2015, sprout wrote​:

On Sat Jan 17 17​:09​:43 2015, hv wrote​:

A perl built with debugging gives​:

% ./perl test.pl
perl​: toke.c​:2430​: S_sublex_done​: Assertion `(PL_parser-

lex_inwhat)
== OP_SUBST || (PL_parser->lex_inwhat) == OP_TRANS' failed.
Aborted (core dumped)
%

Here's a shorter testcase (which doesn't clarify much)​:

% cat test.pl
"$a{m/""$b
/ m ss
";@​c = split /x/
%

We seem to have a PL_lex_repl with PL_lex_inwhat == OP_MATCH, which
clearly
can't happen.

This is no doubt related to my two favourite japhs​:

s||${s/.*/|;
/s}Just another Perl hacker,
print

"${;s/.*/Just an";
other Perl hacker,
/s} die or return;
print

I don’t remember exactly how they work. At the time I wrote them I
based them on the interesting implementation details I saw in toke.c,
which had something to do with PL_sublex_info.sub_inwhat.

skipspace() in toke.c, which is usually used to skip whitespace
between tokens, reads more input from the file if it needs to. But it
checks first to see whether we are in a multiline construct and does
not read more input if we are.

scan_str, which looks for the final delimiter of the string, does not
check whether we are in a multiline construct, but just reads straight
ahead.

So in this japh​:

s||${s/.*/|;
/s}Just another Perl hacker,
print

When the replacement part of the outer s||| is parsed, ‘${s/.*/’ is
the current ‘line’ of input, and scan_str, trying to find the final
delimiter for the s///, reads the next line of input from the stream,
which is ‘/s}Just another Perl hacker,’. It finds its delimiter (and
the /s flag to boot) immediately. Then the inner s/// construct is
exited via S_sublex_done, which sets PL_sublex_info.sub_inwhat to 0
indiscriminately, even though we are still inside the outer string-
like construct.

PL_sublex_info.sub_inwhat is what skipspace checks before reading more
input.

The setting of PL_sublex_info.sub_inwhat to 0 prematurely does not appear to be involved in this particular case.

The current line of input, which is assumed to be the replacement part of the substitution, now contains ‘Just another Perl hacker,’, which is parsed as part of the s||...| and then parsing continues after that.

So

s||${s/.*/|;
/s}Just another Perl hacker,
print

ends up equivalent to

s||${s/.*/
/s}Just another Perl hacker,|;
print

(The purpose of that discourse was to refresh my memory of how this stuff works.)

Now, for the reduced crashing case​:

"$a{m/""$b
/ m ss
";@​c = split /x/

When parsing the initial "$a{m/" string, we find m/.../ inside it, and, again, scan_str reads the next line of input (‘/ m ss’), which is assumed to be inside the first string.

Notice that we have no } on the second line, so I assume the setting of sub_inwhat to 0 has some role here, but I do not yet see how PL_lex_repl gets set. (The assertion fails because PL_lex_repl is set when we are inside a match op.)

--

Father Chrysostomos

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Feb 2, 2015

From @cpansprout

On Sun Feb 01 07​:54​:28 2015, sprout wrote​:

Now, for the reduced crashing case​:

"$a{m/""$b
/ m ss
";@​c = split /x/

When parsing the initial "$a{m/" string, we find m/.../ inside it,
and, again, scan_str reads the next line of input (‘/ m ss’), which is
assumed to be inside the first string.

At this point, m ss is read as another match op. So the parser sees

stringify ( $a{ PMFUNC ( "" ) PMFUNC

and immediately reports this as a syntax error, before we even enter the scope for parsing the inside of m ss. The content of m ss (the empty string) is still inside PL_lex_stuff.

The parser, on encountering the syntax error, pops the savestack in the process of popping tokens, thereby exiting the quote-parsing scope. So we end up back at line 1 with ‘"$b’ as the next thing to be read. However, PL_lex_stuff is not localised, so it does not get cleared in this process, but remains set.

(Usually PL_lex_stuff does not need to be localised, since it is a temporary value that is set when the string is scanned and then read after the next token has been emitted.)

So here we have a string to parse, but PL_lex_stuff is already set, so the string inside "$b<newline>" gets stuffed into PL_sublex_info.repl. The '"'-handling code in yylex then checks PL_lex_stuff to see whether it needs to do double-quote handling. Since PL_lex_stuff contains the empty string from m ss it concludes that this is simply a "" constant, which gets emitted.

We continue parsing line 3, till we get to the /x/, which requires an inner quote-handling scope. ‘x’ is written to PL_lex_stuff. PL_sublex_info.repl is still set from before ("$b\n"), which is placed in PL_lex_repl, so when we finish handling the pattern, S_sublex_done sees that there is a replacement still, which fails an assertion because a match cannot have a replacement part.

Knowing this much, I can reduce it further, and we can trigger the bug even without the "$a{/" hack, which means we have (at least) two separate bugs here​:

$ ./miniperl -e '"$a{ 1 m// }"; //'
Assertion failed​: (PL_lex_inwhat == OP_SUBST || PL_lex_inwhat == OP_TRANS), function S_sublex_done, file toke.c, line 2430.
Abort trap​: 6

Localising PL_lex_stuff will probably fix this last example.

--

Father Chrysostomos

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Feb 5, 2015

From @cpansprout

I have fixed the crash in eabab8b. The oddity with nested quote-like operators overlapping I have left for now, since it is necessary to track down another bug, and it is harmless.

--

Father Chrysostomos

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Feb 5, 2015

@cpansprout - Status changed from 'open' to 'pending release'

@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jun 2, 2015

From @khwilliamson

Thanks for submitting this ticket

The issue should be resolved with the release today of Perl v5.22, available at http​://www.perl.org/get.html
If you find that the problem persists, feel free to reopen this ticket

--
Karl Williamson for the Perl 5 porters team

@p5pRT p5pRT closed this Jun 2, 2015
@p5pRT

This comment has been minimized.

Copy link
Collaborator Author

@p5pRT p5pRT commented Jun 2, 2015

@khwilliamson - Status changed from 'pending release' to 'resolved'

@p5pRT p5pRT added the Severity Low label Oct 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.