Bug 87335 - The stack overflow in function cplus_demangle_type in cp-demangle.c:2565 (c++filt -t)
Summary: The stack overflow in function cplus_demangle_type in cp-demangle.c:2565 (c++...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: unknown
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: ice-on-invalid-code
Depends on:
Blocks:
 
Reported: 2018-09-17 14:56 UTC by Cheng Wen
Modified: 2018-12-07 12:57 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2018-09-18 00:00:00


Attachments
Stack_overflow_in_c++filt-t (56 bytes, text/plain)
2018-09-17 14:56 UTC, Cheng Wen
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Cheng Wen 2018-09-17 14:56:44 UTC
Created attachment 44706 [details]
Stack_overflow_in_c++filt-t

Hi,

We have found a stack overflow in function cplus_demangle_type in cp-demangle.c:2565 in c++filt of the latest binutils code base. 

Here is the POC file. Please use the “c++filt -t < $POC ” to reproduce the bug. Thank you very much.


Command:“c++filt -t < $POC ” (Please remember to use the option -t)

AddressSanitizer:DEADLYSIGNAL
=================================================================
==21814==ERROR: AddressSanitizer: stack-overflow on address 0x7ffcafaefbc0 (pc 0x0000008d3eb1 bp 0x7ffcafaf02d0 sp 0x7ffcafaefbc0 T0)
    #0 0x8d3eb0 in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2367
    #1 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #2 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #3 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #4 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #5 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #6 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #7 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #8 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #9 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    ...
    ...
    ...
    #246 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #247 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #248 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5
    #249 0x8d523c in cplus_demangle_type /binutils-2.31/libiberty/./cp-demangle.c:2565:5

SUMMARY: AddressSanitizer: stack-overflow /binutils-2.31/libiberty/./cp-demangle.c:2367 in cplus_demangle_type
==21814==ABORTING
Aborted
Comment 1 Martin Liška 2018-09-18 08:08:54 UTC
Is the input a valid C++ mangled name of not?
Comment 2 Cheng Wen 2018-09-18 08:46:58 UTC
(In reply to Martin Liška from comment #1)
> Is the input a valid C++ mangled name of not?

Hi,
This input is obtained through fuzzing technology. Our fuzzer get some test cases by mutating a valid input. This can not guarantee that this is a valid C++ mangled name. 

The program c++filt accepts the test case I uploaded. And this test case can prove that c++filt have problems. When program c++filt executing this input, a stack-overflow problem occurs. Please check this input and try to fix this bug if necessary.

Thank you very much.
Comment 3 Jonathan Wakely 2018-09-18 09:15:30 UTC
No, it's not a valid name. I can't reproduce a crash using the latest code from GCC though.
Comment 4 Jonathan Wakely 2018-09-18 09:50:10 UTC
Are you sure you attached the right file? When I try to demangle the attachment it doesn't crash, the __cxa_demangle file returns -2, meaning the name is not valid. That seems like the right result.
Comment 5 Cheng Wen 2018-09-18 09:56:47 UTC
(In reply to Jonathan Wakely from comment #4)
> Are you sure you attached the right file? When I try to demangle the
> attachment it doesn't crash, the __cxa_demangle file returns -2, meaning the
> name is not valid. That seems like the right result.

I have tried to reproduce this bug on different machines.
There are some questions to be confirmed.

(1) Do you use the latest version of binutils(binutils-2.32/binutils-2.31)? I downloaded the package from here.
https://www.gnu.org/software/binutils/

(2) Please confirm that you have used the option "-t".
The command should be "./c++filt -t < $POC"

(3) Do you confirm this POC with address sanitizer?
Comment 6 Jonathan Wakely 2018-09-18 10:23:35 UTC
(In reply to Cheng Wen from comment #5)
> (In reply to Jonathan Wakely from comment #4)
> > Are you sure you attached the right file? When I try to demangle the
> > attachment it doesn't crash, the __cxa_demangle file returns -2, meaning the
> > name is not valid. That seems like the right result.
> 
> I have tried to reproduce this bug on different machines.
> There are some questions to be confirmed.
> 
> (1) Do you use the latest version of binutils(binutils-2.32/binutils-2.31)?
> I downloaded the package from here.
> https://www.gnu.org/software/binutils/

Built from the binutils-gdb git repo:

$ /tmp/binutils/bin/c++filt -v
GNU c++filt (GNU Binutils) 2.31.51.20180918
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) any later version.
This program has absolutely no warranty.


> (2) Please confirm that you have used the option "-t".
> The command should be "./c++filt -t < $POC"

$ /tmp/binutils/bin/c++filt -t < POC-t | wc 
      0       1   26539
$ echo $?
0


> (3) Do you confirm this POC with address sanitizer?

Yes it's linked to libasan

$ ldd /tmp/binutils/bin/c++filt 
        linux-vdso.so.1 (0x00007fff0618b000)
        libasan.so.4 => /lib64/libasan.so.4 (0x00007fc372241000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fc37203d000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fc371c87000)
        librt.so.1 => /lib64/librt.so.1 (0x00007fc371a7f000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc371861000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fc3714d9000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fc37118e000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc370f77000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fc3731f9000)
Comment 7 Cheng Wen 2018-09-18 10:28:32 UTC
(In reply to Jonathan Wakely from comment #6)

Considering the memory size of different machines, maybe more 'P' is needed to trigger this bug in the input.
Comment 8 Jonathan Wakely 2018-09-18 10:34:17 UTC
It still works for up to ten million characters:

$ for i in `seq 1 10` ; do printf P ; done | /tmp/binutils/bin/c++filt -t ; echo
PPPPPPPPPP
$ for i in `seq 1 100` ; do printf P ; done | /tmp/binutils/bin/c++filt -t | wc 
      0       1     100
$ for i in `seq 1 1000` ; do printf P ; done | /tmp/binutils/bin/c++filt -t | wc 
      0       1    1000
$ for i in `seq 1 10000` ; do printf P ; done | /tmp/binutils/bin/c++filt -t | wc 
      0       1   10000
$ for i in `seq 1 100000` ; do printf P ; done | /tmp/binutils/bin/c++filt -t | wc 
      0       1  100000
$ for i in `seq 1 1000000` ; do printf P ; done | /tmp/binutils/bin/c++filt -t | wc 
      0       1 1000000
$ for i in `seq 1 10000000` ; do printf P ; done | /tmp/binutils/bin/c++filt -t | wc 
      0       1 10000000
Comment 9 Cheng Wen 2018-09-18 12:53:44 UTC
(In reply to Jonathan Wakely from comment #8)

Hi Jonathan,

I debugged with this POC again. I still think it's a problem. I will show you the debug process as follow.

> $ gdb ./c++filt
> Reading symbols from ./c++filt...done.
> (gdb) set args -t < POC-t
> (gdb) b cp-demangle.c:2565
> Breakpoint 1 at 0x8d5227: file ./cp-demangle.c, line 2565.
> (gdb) start
> (gdb) c
> Continuing.
> Breakpoint 1, cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> 2565	cplus_demangle_type (di), NULL);
> (gdb) c
> Continuing.
> Breakpoint 1, cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> 2565	cplus_demangle_type (di), NULL);
> ...
> ...
> ...
> (gdb) c
> Continuing.
> Breakpoint 1, cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> 2565	cplus_demangle_type (di), NULL);
> (gdb) bt
> #0  cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> #1  0x00000000008d523d in cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> #2  0x00000000008d523d in cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> #3  0x00000000008d523d in cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> #4  0x00000000008d523d in cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> ...
> ...
> ...
> #456 0x00000000008d523d in cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> #457 0x00000000008d523d in cplus_demangle_type (di=0x7fffffffd560) at ./cp-demangle.c:2565
> #458 0x00000000008dd318 in d_demangle_callback (mangled=0x18b2e40 <main.mbuffer> 'P' <repeats 200 times>..., options=283,
>   callback=0x8dc110 <d_growable_string_callback_adapter>, opaque=0x7fffffffd860) at ./cp-demangle.c:6245
> #459 0x00000000008dc84f in d_demangle (mangled=0x18b2e40 <main.mbuffer> 'P' <repeats 200 times>..., options=283,
>   palc=0x7fffffffd9e0) at ./cp-demangle.c:6299
> #460 0x00000000008dc696 in cplus_demangle_v3 (mangled=0x18b2e40 <main.mbuffer> 'P' <repeats 200 times>..., options=283)
>   at ./cp-demangle.c:6456
> #461 0x00000000008b1cf4 in cplus_demangle (mangled=0x18b2e40 <main.mbuffer> 'P' <repeats 200 times>..., options=27)
>   at ./cplus-dem.c:880
> #462 0x0000000000517676 in demangle_it (mangled_name=0x18b2e40 <main.mbuffer> 'P' <repeats 200 times>...) at cxxfilt.c:62
> #463 0x000000000051726a in main (argc=2, argv=0x7fffffffe008) at cxxfilt.c:276


Using gdb to debug it. I set a breakpoint in cp-demangle.c:2565. After reaching this breakpoint for any time. You can see the stack backtrace.
This will consume a lot of stack memory.
(Caution: the command such as "gdb --args ./c++filt -t < $POC" is not valid. Please use "gdb ./c++filt", then "set args -t < $POC")

Thanks
Cheng Wen
Comment 10 Scott Gayou 2018-11-29 17:13:06 UTC
This reproducer seems to require adjusting the maximum stack size.

i.e.:

$ ulimit -s 8192 && c++filt < poc -t

does NOT crash

whereas:

$ ulimit -s 2048 && c++filt < poc -t
Segmentation fault (core dumped)

This looks to be another potentially duplicated CVE.

See the following:

CVE-2018-18484: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87636
CVE-2018-18701: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87675
CVE-2018-18700: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87681

All appear to be the same root cause.
Comment 11 Cheng Wen 2018-11-30 04:35:37 UTC
(In reply to Scott Gayou from comment #10)

> does NOT crash

That depends on your compilation options. Because stack memory is very small, generally only 1M to 2M. You can debug it with GDB and see the backtrace.

> This looks to be another potentially duplicated CVE.

Unlike several other errors, this error is to call itself. In addition, This problem was discovered earlier than those CVEs.

> All appear to be the same root cause.

Let's analyze the source code.

struct demangle_component * 
cplus_demangle_type (struct d_info *di) {
  switch (peek)
  {
    // ...
    case 'F':
      ret = d_function_type (di); break;
    // ...
    case 'P':
      ret = d_make_comp (di, DEMANGLE_COMPONENT_POINTER,
			 cplus_demangle_type (di), NULL);
      break;
    case 'C':
      ret = d_make_comp (di, DEMANGLE_COMPONENT_COMPLEX,
			 cplus_demangle_type (di), NULL);
      break;
    case 'G':
      ret = d_make_comp (di, DEMANGLE_COMPONENT_IMAGINARY,
			 cplus_demangle_type (di), NULL);
      break;
    // ...
  }
  // ...
}

Intuitively, in some cases, function cplus_demangle_type shows the behavior of recursive calls. When the function cplus_demangle_type receive character 'P'(The same as 'C' and 'G'), the cplus_demangle_type function making recursive calls to itself(Line 13, 18, 23). Another situation is that the function receive character 'F', then there's a recursed stack frame: cplus_demangle_type, d_bare_function_type, d_function_type(Line 8, 32, 39).

So different stack memory exhaustion can lead to stack memory exhaustion DoS. That depends on your compilation options. You can use my compilation options.

> CC=clang LDFLAGS="-ldl" CFLAGS="-DFORTIFY_SOURCE=2 -fstack-protector-all -fsanitize=undefined,address -fno-omit-frame-pointer -g -O0 -Wno-error" ./configure --disable-shared --disable-gdb --disable-libdecnumber --disable-sim

If you have any question, please let me know.
Comment 12 Nick Clifton 2018-12-07 10:34:03 UTC
Author: nickc
Date: Fri Dec  7 10:33:30 2018
New Revision: 266886

URL: https://gcc.gnu.org/viewcvs?rev=266886&root=gcc&view=rev
Log:
Add a recursion limit to libiberty's demangling code.  The limit is enabled by default, but can be disabled via a new demangling option.

include	* demangle.h (DMGL_NO_RECURSE_LIMIT): Define.
        (DEMANGLE_RECURSION_LIMIT): Define

	PR 87681
	PR 87675
	PR 87636
	PR 87350
	PR 87335
libiberty * cp-demangle.h (struct d_info): Add recursion_level field.
	* cp-demangle.c (d_function_type): Add recursion counter.
	If the recursion limit is reached and the check is not disabled,
	then return with a failure result.
	(cplus_demangle_init_info): Initialise the recursion_level field.
        (d_demangle_callback): If the recursion limit is enabled, check
	for a mangled string that is so long that there is not enough
	stack space for the local arrays.
        * cplus-dem.c (struct work): Add recursion_level field.
	(squangle_mop_up): Set the numb and numk fields to zero.
	(work_stuff_copy_to_from): Handle the case where a btypevec or 
	ktypevec field is NULL.
	(demangle_nested_args): Add recursion counter.  If
	the recursion limit is not disabled and reached, return with a
	failure result.

Modified:
    trunk/include/ChangeLog
    trunk/include/demangle.h
    trunk/libiberty/ChangeLog
    trunk/libiberty/cp-demangle.c
    trunk/libiberty/cp-demangle.h
    trunk/libiberty/cplus-dem.c
Comment 13 Nick Clifton 2018-12-07 12:57:29 UTC
Fixed by commit 266886.