Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory corruption with a regular cast? Wrong function called

I have the following small program:

#include <iostream>
#include <map>

using namespace std;

class A {
public:
    virtual void hello(int i)
    {
        cout << "A Hello " << i << endl;
    };
};

class B {
public:
    virtual void nothing() = 0;
};

class C : public A, public B {
public:
    virtual void hello(int i) override
    {
        cout << "C Hello " << i << endl;
    };

    virtual void nothing() override
    {
        cout << "C Nothing " << endl;
    }
};

int main() {
    map<int, B*> map_;

    A* testA = new C();
    map_[0] = (B*)testA;
    B* myB = static_cast<B*>(map_[0]);
    myB->nothing();

    C* testC = new C();
    map_[1] = (B*)testC;
    myB = static_cast<B*>(map_[1]);
    myB->nothing();

    return 0;
}

As an output, I was expecting the following:

C Nothing
C Nothing 

But here is what I get:

C Hello 0
C Nothing 

So the wrong function is called: hello(int i) gets called even though it is never called in the code. I understand it has something to do with casts but I can't understand where is the mistake.

Why is hello(int i) called?

like image 652
MartinMoizard Avatar asked Sep 02 '14 08:09

MartinMoizard


People also ask

What is memory corruption exploit?

Definition: Memory corruption can be described as the vulnerability that may occur in a computer system when its memory is altered without an explicit assignment. The contents of a memory location are modified due to programming errors which enable attackers to execute an arbitrary code.

What causes memory corruption in C?

Pointer variable can point to a invalid memory location which can cause access violation and a crash. Memory corruption may occur because of poor array buffer handling or some abnormal runtime use-cases.


2 Answers

The conversion between base-class pointers (sometimes known as cross-casting) requires dynamic_cast. Given a pointer to A there's no way to know (statically) that it's part of a C object, and therefore no way to statically find the corresponding B subobject.

What you call a "regular" cast is the most dangerous type of cast. You should never use that style of cast (at least, not to convert pointers or references). It will force the conversion using any cast it can - apart from dynamic_cast. So in this case, it's equivalent to reinterpret_cast, pretending that there's a B object at the same address as the A object. Since there isn't a B there (it's somewhere else within the C), you get undefined behaviour.

(The specific flavour or undefined behaviour is probably that it uses the virtual table entry that would contain nothing in a B, but actually contains hello, and so ends up calling that, with some undefined value as the argument. But of course, being undefined, anything could happen in principle.)

like image 100
Mike Seymour Avatar answered Sep 27 '22 16:09

Mike Seymour


Do not use C-style casts, especially when working with inheritance hierarchies. In your case, instead of (B*)testA you should be using dynamic_cast<B*>(testA), and then checking whether or not it returns NULL. This is needed, because C++ compiler does not know whether type of object pointed to by testA is inherited from B, and if yes, how to adjust the pointer value to reflect that.

So what you run into is an undefined behavior, that may occur because C++ implementation uses either wrong vtable, or wrong offset into vtable.

like image 40
mephi42 Avatar answered Sep 27 '22 18:09

mephi42