- Vulnerabilities
- Memory allocation
Programs treat memory as a place to store data and as a way to refer back to that data later. At runtime, programs constantly create, use, and discard data structures. Memory safety means: the program only reads and writes memory it actually owns, and only while that memory is valid. This section covers common failure modes, and then the memory allocation strategies that can make it easier to avoid them.
Vulnerabilities
Memory bugs are dangerous because they break a basic assumption: that a piece of code is only reading and writing the memory it intended to use. When that assumption fails, the best case is a clean crash. The worse case is silent corruption: the program keeps running, but with incorrect data, incorrect control flow, or unexpected exposure of sensitive information.
Use after free happens when code keeps a pointer to heap memory, frees it, and then later reads or writes through the old pointer anyway. Sometimes this crashes immediately (for example if the memory was unmapped). More dangerously, it often does not crash: the allocator may have already reused that block for a different purpose, so the stale pointer now aliases a different object. That can cause "impossible" behavior, data corruption, or security vulnerabilities, because the program is now accidentally reading or writing the wrong thing.
Out of bounds write or read happens when code reads or writes past the end of an array or buffer. This can crash if it crosses into an unmapped or protected page, but it can also silently read unrelated data or corrupt nearby objects. Out-of-bounds reads can leak secrets (because you end up reading bytes that were never meant to be exposed). Out-of-bounds writes can corrupt control data (function pointers, return addresses, allocator metadata) and can sometimes be used by an attacker to take control of the program.
Null dereferencing happens when code tries to read or write through a pointer that has an address value of zero. On most modern systems, address zero is intentionally left unmapped in user space, so this causes an immediate crash (often reported as a segmentation fault). In some low-level contexts, address zero may be treated specially, or a bug might accidentally make it accessible, which can turn "it crashes" into "it behaves strangely".
Memory leaks happen when the program allocates heap memory but never frees it, and also loses the last reference to it. That memory stays reserved for the process even though the program can no longer use it. It's not really a memory vulnerability, and in short-lived programs this may not matter much because the OS reclaims memory when the process exits. But in long-running programs, leaks accumulate: memory usage grows, performance can drop, swapping can occur, and the process can eventually be killed for using too much memory.
Not all bugs are "memory bugs", but many non-memory bugs can still lead to memory safety problems or memory exhaustion. The common pattern is that logic errors or resource-management errors cause the program to compute wrong sizes, skip checks, or get stuck holding resources indefinitely.
Memory allocation
"Allocation" is how a program obtains memory for its data structures. Different parts of memory have different allocation rules and different performance characteristics. The more complicated your allocation strategy is, the more opportunities you create for using memory after it stopped being valid, or for simply losing track of it. A good default is to choose the simplest model that fits the data.
Static memory (the one used for global and
static variables) exists for the entire lifetime of the program and it's the simplest to manage. It is there when the program starts and it is still there when the program ends. Because you do not repeatedly create and destroy it at runtime, there is no "did we free it too early" and no "did we forget to free it". The tradeoff is flexibility: you can’t easily size it based on runtime input.Stack memory is a good fit for short-lived working data. When a function runs, it has space for local variables and temporaries, and when the function returns that space is reclaimed automatically. This makes lifetime rules clear and reduces cleanup mistakes. The tradeoff is its limited lifetime: stack memory is only valid while the function (or scope) that created it is active. When the function returns, that space can be reused immediately by later calls. The stack also has a size limit, so very deep call chains (or huge local buffers) can overflow it.
Heap allocation has more overhead than stack allocation because someone must track which heap blocks are in use, how big they are, and where free space exists.
A common interface for heap allocation is:
malloc(size): request a block of at least "size" bytes and get a pointer to itfree(ptr): return a previously allocated block back to the allocator
A lot of performance problems and bugs come from making many small allocations on the heap and trying to keep track of all of them. A common improvement is to group allocations so lifetimes become simpler and more structured. Instead of "allocate and free each object independently", if many objects are supposed to have the same lifetime, allocate them together and free them together. A common implementation of this is the arena allocator: you allocate many related objects from one big chunk, then release them all at once when you don't need them anymore. This reduces the number of individual frees, reduces fragmentation, and makes it harder to leak memory by forgetting to free a single object, because cleanup happens at the group level.