Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looking for explanation for a stack corruption bug

The following problem is distilled from a huge project and the most minimal example of the problem I was able to come up with.

I know, deriving from std::string is bad, and it already is changed in our code base, but I am trying to understand what is happening under the hood here.

The code crashes on Visual C++ 2017

Microsoft Visual Studio Community 2017 
Version 15.2 (26430.14) Release
Visual C++ 2017   00369-60000-00001-AA257

in release-mode only (with speed optimization). It does not crash in release-mode without speed optimization.

#include <string>
#include <string_view>
#include <vector>

struct my_string : public std::string
{
    __declspec(noinline)
        my_string::my_string( const std::string_view& str ) :
        std::string( str.data(), str.size() )
    {}

    template <typename T>
    my_string& arg( T )
    {
        return *this;
    }
};

struct my_string_view : public std::string_view
{
    my_string_view( const std::string_view::value_type* val ) :
        std::string_view( val ) {}

    template <typename... PARAMS>
    my_string arg( PARAMS&&... prms ) {
        return my_string( *this ).arg( std::forward<PARAMS>( prms )... );
    }
};

template <typename T>
struct basic_color
{
    T r, g, b, a;

    basic_color() : r( 0 ), g( 0 ), b( 0 ), a( 255 ) {}

    template <typename U>
    explicit basic_color( const basic_color<U>& c ) :
        r( c.r ), g( c.g ), b( c.b ), a( c.a )
    {}
};

using color = basic_color<std::uint8_t>;
using float_color = basic_color<float>;

__declspec(noinline)
void change_float_color( float_color& color )
{
    color.r = 0.1f;
}

int main()
{
    std::vector<float_color> colors = { {} };
    float sum = 0;
    for ( std::uint32_t i = 0; i < 1; ++i )
    {
        float_color fc;
        change_float_color( fc );
        color c( fc );
        std::vector<std::string> msgs;
        msgs.push_back( my_string_view( "" ).arg( c.r ) );
        msgs.push_back( my_string_view( "" ).arg( c.g ) );
        sum += fc.b - colors[i].b;
    }
    return static_cast<int>(sqrt( sum ));
}

The error in Visual Studio is this (have a look at the broken sizes of msgs and colors at the bottom):

enter image description here

My guess is that the call of std::vector<std::string>::push_back(std::string&&) with a my_string is problematic (slicing-like behavior). But how can this corrupt the stack (or the stack pointer)?

Does anybody have an idea what could be happening here or how I can find out?

Here is my project in case anyone is interested in reproducing the problem.

like image 299
Tobias Hermann Avatar asked Jun 29 '17 10:06

Tobias Hermann


Video Answer


1 Answers

I think it is a compiler bug.

Here's what I see from the disassembly: upon main() entry, esp is saved into ebx. At the end, esp is restored from ebx. However, in the middle (after calling std::_Destroy_range1) ebx value is overwritten with something else. So, at the end, the ret instruction uses a bogus esp value, and jumps to an invalid place.

So, actually, the stack isn't corrupted (this bug cannot be catched with data breakpoints as Hans suggested), but the stack pointer is.

like image 80
geza Avatar answered Oct 13 '22 01:10

geza