C++ code segfaults when compiled -O with Apple's LLVM compiler, but not with g++ -7.2.0

后端 未结 1 624
孤独总比滥情好
孤独总比滥情好 2021-02-08 07:04

Update: I\'ve created an even more M, but still CVE that reproduces the crash. Summary: removed all use of the Bool* bools_ field in Base class (but it

1条回答
  •  抹茶落季
    2021-02-08 07:17

    This looks like a bug in Clang caused by invalid generation of unaligned SSE stores. Below is a minimal example based on your code:

    struct alignas(16) Base1 { };
    
    struct Base2 : virtual Base1 {
        __attribute__((noinline)) Base2() : data1_(0), data2_(0) { }
    
        long dummy_, data1_, data2_;
    };
    
    struct Base3 : virtual Base2 { };
    
    int main() { Base3 obj; }
    

    This is layout generated by Clang (GCC uses the same layout):

    *** Dumping AST Record Layout
             0 | struct Base1 (empty)
               | [sizeof=16, dsize=16, align=16,
               |  nvsize=16, nvalign=16]
    
    *** Dumping AST Record Layout
             0 | struct Base2
             0 |   (Base2 vtable pointer)
             8 |   long dummy_
            16 |   long data1_
            24 |   long data2_
             0 |   struct Base1 (virtual base) (empty)
               | [sizeof=32, dsize=32, align=16,
               |  nvsize=32, nvalign=8]
    
    *** Dumping AST Record Layout
             0 | struct Base3
             0 |   (Base3 vtable pointer)
             0 |   struct Base1 (virtual base) (empty)
             8 |   struct Base2 (virtual base)
             8 |     (Base2 vtable pointer)
            16 |     long dummy_
            24 |     long data1_
            32 |     long data2_
               | [sizeof=48, dsize=40, align=16,
               |  nvsize=8, nvalign=8]
    

    We can see that Base3 is merged with Base1, so they share address. Base2 is instantiated by Base3 and is placed afterwards with 8 byte offset, aligning Base2 instance at 8 bytes, even though alignof(Base2) is 16. This is still correct behavior, since this is the maximum alignment among all member fields in Base2. Alignment inherited from virtual base class Base1 does not need to be preserved, as Base1 is instantiated by derived class Base3 which is responsible for aligning Base1 properly.

    The problem is with the code Clang generates:

    mov    rbx,rdi ; rdi contains this pointer
    ...
    xorps  xmm0,xmm0
    movaps XMMWORD PTR [rbx+0x10],xmm0
    

    Clang decides to initialize both data1_ and data2_ with a single movaps instruction which requires 16 byte alignment, but Base2 instance is only 8-bytes aligned, leading to segfault.

    Looks like Clang assumes it can use 16-byte aligned stores because alignof(Base2) is 16, but such assumption is wrong for classes with virtual bases.

    If you need a temporary solution, you can disable usage of SSE instructions with -mno-sse flag. Note that this can have performance impact.


    Itanium ABI document can be found here: https://refspecs.linuxfoundation.org/cxxabi-1.75.html

    It explicitly mentions nvalign:

    nvalign(O): the non-virtual alignment of an object, which is the alignment of O without virtual bases.

    Then there is an explanation on how allocation is done:

    Allocation of Members Other Than Virtual Bases

    If D is not an empty base class or D is a data member: Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless doing so would result in two components (direct or indirect) of the same type having the same offset. If such a component type conflict occurs, increment the candidate offset by nvalign(D) for base classes or by align(D) for data members and try again, repeating until success occurs (which will occur no later than sizeof(C) rounded up to the required alignment).

    Looks like both Clang and GCC honor Itanium ABI, properly aligning Base2 using non-virtual alignment. We can also see that in the record layout dump above.


    You can compile your program with -fsanitize=undefined (both GCC and Clang) to get this false-positive warning message at runtime:

    main.cpp:29:5: runtime error: constructor call on misaligned address 0x7ffd3b895dd8 for type 'Base2', which requires 16 byte alignment
    0x7ffd3b895dd8: note: pointer points here
     e9 55 00 00  ea c6 2e 02 9b 7f 00 00  01 00 00 00 00 00 00 00  02 00 00 00 00 00 00 00  f8 97 95 34
    

    So there are three bugs at the moment. I have reported all of them:

    • Clang's unaligned movaps bug: https://bugs.llvm.org/show_bug.cgi?id=35901
    • Clang's sanitizer bug: https://bugs.llvm.org/show_bug.cgi?id=35902
    • GCC's sanitizer bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83780

    0 讨论(0)
提交回复
热议问题