10

I know that Undefined Behaviour, once it has happened, makes it impossible to think about the code any longer. I am convinced, completely. I even think I should not dig too much into understanding UB: a sane C++ program should not play with UB, Period.

But so as to convince my colleagues and managers about the real danger of it, I try to find a concrete example, with a bug we DO have in the product (about which they think it is not dangerous, at worst it will always crash with an access violation).


My main concern is about calling a virtual member function on dangling pointers to polymorphic class.

When a pointer is deleted, the windows OS will write a few bytes in the header of the heap block, and usually overwrites also the first bytes of the heap block itself. This is its way to keep track of heap blocks, manage them as a linked list... OS stuffs.

Though it's not defined in the C++ standard, polymorphism is implemented using virtual tables, AFAIK. Under windows, the pointer to the virtual table is located in the first bytes of the heap block, given a class that inherits only one base class. (It may be more complex with multi-inheritance, but I will not take this into account. Let's only consider base class A, and several B, C, D inheriting A).


Now let's consider I have a pointer to an A, which was instanciated as a D objects. And that D object has been deleted elsewhere in the code: so the heap block is now a free heap block, and its first bytes has been overwritten, and as a consequence the virtual table pointer is pointing almost at random somewhere in memory, let's say the address 0x01234567.

When somewhere in the code, we call:

void test(A * pA)   
{
    # here we do not know that pA is dangling pointer
    # that memory address has been deleted by another thread, in another part of the code
    pA->SomeVirtualFunction();
}

Am I right telling that:

  • the runtime will interpret the memory at address 0x01234567 as if it was a virtual table
  • if while interpreting falsely this memory address like a vtable, it doesn't go to forbidden memory zone, there will not be any Access Violation
  • the misinterpreted virtual table will provide a random address for the virtual function to execute, let's say 0x09876543
  • the memory at the random addres 0x09876543 will be interpreted as valid binary code, and EXECUTED for real
  • this can lead to ANY corruption imaginable

I don't want to be exaggerating, so as to convince. So, is what I'm saying is correct, possible, and likely ?

Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
  • Strictly speaking UB can trigger whatever behaviour the compiler likes. In practice, most compilers produce roughly deterministic code anyway, so if your program passes its tests, you're probably OK until you change compiler (or version). E.g. signed integer overflow is UB, but probably innocuous. It may cost too much to remove all the UB in a "working" product. You are technically correct though, and that's the best kind of correct. Good luck – Jon Chesterfield Jun 21 '15 at 17:26
  • 5
    From a security standpoint, use-after-free bugs are probably the second easiest kind of bug to exploit after buffer overflows. Virtual tables make the attack a lot easier. An attacker can basically force something to be allocated onto the previous memory space, write some attack code there, then trigger your bug to take control of the program. Bam - you're hacked. – nneonneo Jun 21 '15 at 17:31
  • 2
    If another object gets allocated at the same location, then you are going to call through that *other object's* vtable, and that means basically calling a random function with random parameters. – Raymond Chen Jun 21 '15 at 18:39

1 Answers1

3

Your example is a possibility.

However, the situation is much, much worse.

If someone is attacking users of your application, then the memory will not contain random data. The attacker will try and likely manage to influence what that data will be. Once that happens, the attacker may be able to determine which code will be executed. And once that happens, unless your application is properly sandboxed (which I bet it is not with that attitude of your co-developers), the attacker may be able to take over the user's computer.

And that's not a hypothetical possibility, but something that has happened and will happen again.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • For the moment my main concern is about unexplained corruptions. I think security may come a little after. – Stephane Rolland Jun 21 '15 at 18:32
  • 1
    @StephaneRolland If malicious take-over is possible, so is *any* kind of memory corruption. Both the screaming kind which crashes your application and the evil silent kind that "just" leads to wrong results. Just imagine what would happen if the memory of the dangling pointer has already been reused for another polymorphic object. The virtual call will likely resolve to a member function of that new object, which will get random arguments. That member function may change the state of the object in a way that the code that's actually using the object won't expect. Madness ensues... – cmaster - reinstate monica Jun 21 '15 at 20:19