Bestiary of a Poorly Managed Memory (IV)

David García    25 May, 2020
Bestiary of a Poorly Managed Memory (IV)

If we must choose a particularly damaging vulnerability, it would most likely be arbitrary code execution, and even more so if it can be exploited remotely. In the first blog entry we introduced the issues that can be caused by a poorly managed memory. The second one was about double free, and the third one was focused on dangling pointers and memory leaks. Let’s close this set of blog posts with the use of uninitialized memory and the conclusions.

Use of Uninitialized Memory

For efficiency purposes, when we call ‘malloc’ or use the ‘new’ operator in C++, the memory area allocated is not initialized. What does this mean? That it doesn’t contain a default value, but data that will seem random to us and doesn’t make sense in the context of our process. Let’s see it:

We get a block of 10,000 integers, fill it with random integers and free it up. In theory, according to the standard of the C library, the memory coming from ‘malloc’ should not be initialized, but in certain systems (particularly modern ones) it is likely to be initialized to zero. That is, the whole reserved area is full of zeros.

In the program, we make use of a reserved memory area and then free it up. But when we use this type of memory again, the system returns that same block with the content it already had. This content probably does not make sense in the current execution context.

Let’s see the output:

What would happen if we used that data accidentally? Let’s see it, again, within code. We modify the program so that the second reserve is made for a structure that we have defined:

As we can see, we make use of ‘p’ by filling that area with random data. We free that block and now call one for a structure that should contain a pointer to a string and two integer values. Let’s check a series of executions:

As we see, the structure is initialized with “garbage”, and making use of this “garbage” is problematic, if not worrying, and completely unsecure. Imagine that these values are used for a critical application.

In addition to those already mentioned, the issues related to manual memory management do not end here. What we have seen is just a small sample and the list would be endless: pointer arithmetic, out-of-bounds write, and so on.

New Management Mechanisms

C++ has greatly improved manual management so that if already in the first steps of the language the need to use functions through operators (“new” and “delete”) was removed, the new standard extends and improves memory management through “smart” pointers. That is, memory containers that call their own destructor when they detect that they are no longer useful. For example, when an object goes out of a scope and is no longer referenced by any other variable or object.

Still, even with smart pointers, there is room for surprise and even for cases where we must use the traditional method, either for efficiency or for limitations in the libraries used by a given application.

Another method of memory management that does not require a collector is the system used by languages such as Swift or Rust. The first one uses an “immediate” type of memory collector that does not require pauses, ARC or Automatic Reference Counting. This is a method that relies on the compiler to insert into the code the appropriate instructions to free up memory when this one is no longer going to be used. Rust, a relatively modern language, uses a method based on the concepts of “borrowing” and “ownership” related to the objects created with dynamic memory. An intermediate commitment between not having to carry the burden of a memory collector and the inconvenience of the programmer having to worry minimally about the logic of “borrowing” an object to other methods.

Conclusions

It is clear that manual memory management causes a tremendous vortex of issues that can (and usually do) lead to serious security problems. On the other hand, it requires a good capacity, attention and experience from programmers who use languages like C or C++. This doesn’t mean that these languages should be abandoned because they are complex to use in certain aspects. As we said at the beginning, you can’t avoid using them in certain types of applications.


Don’t miss the previous entries of this post:

Leave a Reply

Your email address will not be published. Required fields are marked *