This is the mail archive of the gcc-bugs@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug c++/67918] New: -fdevirtualize causes binary to crash with Segmentation Fault (CryptoPP involved)


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67918

            Bug ID: 67918
           Summary: -fdevirtualize causes binary to crash with
                    Segmentation Fault (CryptoPP involved)
           Product: gcc
           Version: 5.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: 11throwaway11 at outlook dot com
  Target Milestone: ---

Created attachment 36473
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=36473&action=edit
preprocessed file

Let me preface this bug report by saying that the following code compiles just
fine on clang with -O3, on MSVC with their maximum optimizations and on g++
with -O1. And notice what weird steps (mentioned below) make the gcc build a
binary that works well.

I'll try to make this report as useful as I can, but keep in mind that I'm
kinda rookie with compiling/debugging on Linux.

I've removed as much code from my project as possible to still be able
reproduce the bug.

Then I noticed something strange: removing declarations&definitions of
functions that are not even called anywhere (but they were used in my original
project) fixes the problem, so I made a file called fix1.cpp w/o these
functions.

After using valgrind with my original project, I remember it pointed to the
destructors of CryptoPP::*::(En-/De-)cryption objects. So I thought, what if I
allocate those dynamically instead of statically and do not use 'delete' (=>
never call the destructors), it worked. Then I added delete operators, and it
still worked. Weird. So I made a file called fix2.cpp containing this approach.
(but in this minimal testcase valgrind points to the destructor of the
basic_string, although dynamically allocating CryptoPP's objects and still
statically allocating std::string does the job, thus STL has NOTHING to do with
it).

Then I learned about -fsanitize=undefined, tried it: "execution reached
__builtin_unreachable() call", tried to search what exactly could cause this,
didn't find anything remotely useful.

So I thought, since clang compiles it just fine, what if I use their
-fsanitize=undefined, got: "null pointer passed as argument" and it pointed to
a /cryptopp/misc.h, line #149. They were calling memcpy without nullptr check.
Alright, fine, thought I. Added such a check. Recompiled with clang, fsanitize
was silent this time.

So it's time to recompile with gcc, thought I. No, still crashes. Still the
same "__builtin_unreachable()".
But it's silent for binaries created from fix1.cpp and fix2.cpp.

-Wall -Wextra shows no warnings.
-fno-strict-aliasing -fwrapv -fno-aggressive-loop-optimizations makes no
difference.

Although I checked what's the difference between -O1 and -O2 and narrowed it
down to -fdevirtualize.
-O1 -fdevirtualize makes testcase.cpp create a corrupted binary.

And it makes the same thing with fix1.cpp, but it worked fine with -O2.
Alright, -O1 -fdevirtualize -fdevirtualize-speculatively makes fix1.cpp create
a correct binary.

Here's what I use to compile: g++ --std=c++11 -O3 -g -Wall -Wextra -save-temps
-lcryptopp -lpthread

Seems I can't attach more than 1 file (I'll try to add remaining .ii files in
the comments though), so here's the link to a dropbox folder with everything
(temp files, binaries compiled with -g, sources, makefile,
gdb/valgrind/fsanitize outputs):
https://www.dropbox.com/sh/0vr8i0mwuf2guk4/AADgQDd3S24v2Z7JzNejD71ja?dl=0

Below are the outputs of gcc -v, gdb bt and valgrind.

$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-unknown-linux-gnu/5.2.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with: /build/gcc/src/gcc-5.2.0/configure --prefix=/usr
--libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man
--infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/
--enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared
--enable-threads=posix --enable-libmpx --with-system-zlib --with-isl
--enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu
--disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object
--enable-linker-build-id --enable-lto --enable-plugin
--enable-install-libiberty --with-linker-hash-style=gnu
--enable-gnu-indirect-function --disable-multilib --disable-werror
--enable-checking=release --with-default-libstdcxx-abi=gcc4-compatible
Thread model: posix
gcc version 5.2.0 (GCC) 

(gdb) file testcase
Reading symbols from testcase...done.
(gdb) run
Starting program: /home/nick/Documents/CryptoPP/TESTCASE/testcase 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
_Unwind_Resume (exc=exc@entry=0x0)
    at /build/gcc/src/gcc-5.2.0/libgcc/unwind.inc:229
229     /build/gcc/src/gcc-5.2.0/libgcc/unwind.inc: No such file or directory.
(gdb) bt
#0  _Unwind_Resume (exc=exc@entry=0x0)
    at /build/gcc/src/gcc-5.2.0/libgcc/unwind.inc:229
#1  0x0000000000408c97 in std::string::_Rep::_M_dispose (__a=..., 
    this=<optimized out>) at /usr/include/c++/5.2.0/bits/basic_string.h:2636
#2  AESEncrypt (source="test", key=key@entry=0x7fffffffe7f0 "123456789012345", 
    keylength=keylength@entry=16, 
    iv=iv@entry=0x7fffffffe800 "1234567890123456")
    at /usr/include/c++/5.2.0/bits/basic_string.h:2943
#3  0x00000000004086f6 in main () at testcase.cpp:23

$ valgrind ./testcase
==1227== Memcheck, a memory error detector
==1227== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1227== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==1227== Command: ./testcase
==1227== 
==1227== Invalid read of size 8
==1227==    at 0x5CFB3D7: _Unwind_Resume (unwind.inc:229)
==1227==    by 0x408C96: _M_dispose (basic_string.h:2636)
==1227==    by 0x408C96: AESEncrypt(std::string const&, unsigned char*,
unsigned long, unsigned char*) (basic_string.h:2943)
==1227==    by 0x4086F5: main (testcase.cpp:23)
==1227==  Address 0x10 is not stack'd, malloc'd or (recently) free'd
==1227== 
==1227== 
==1227== Process terminating with default action of signal 11 (SIGSEGV)
==1227==  Access not within mapped region at address 0x10
==1227==    at 0x5CFB3D7: _Unwind_Resume (unwind.inc:229)
==1227==    by 0x408C96: _M_dispose (basic_string.h:2636)
==1227==    by 0x408C96: AESEncrypt(std::string const&, unsigned char*,
unsigned long, unsigned char*) (basic_string.h:2943)
==1227==    by 0x4086F5: main (testcase.cpp:23)
==1227==  If you believe this happened as a result of a stack
==1227==  overflow in your program's main thread (unlikely but
==1227==  possible), you can try to increase the size of the
==1227==  main thread stack using the --main-stacksize= flag.
==1227==  The main thread stack size used in this run was 8388608.
==1227== 
==1227== HEAP SUMMARY:
==1227==     in use at exit: 72,769 bytes in 4 blocks
==1227==   total heap usage: 4 allocs, 0 frees, 72,769 bytes allocated
==1227== 
==1227== LEAK SUMMARY:
==1227==    definitely lost: 0 bytes in 0 blocks
==1227==    indirectly lost: 0 bytes in 0 blocks
==1227==      possibly lost: 0 bytes in 0 blocks
==1227==    still reachable: 72,769 bytes in 4 blocks
==1227==                       of which reachable via heuristic:
==1227==                         stdstring          : 57 bytes in 2 blocks
==1227==         suppressed: 0 bytes in 0 blocks
==1227== Rerun with --leak-check=full to see details of leaked memory
==1227== 
==1227== For counts of detected and suppressed errors, rerun with: -v
==1227== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]