Memory safety is a concern in software development that aims to avoid software bugs that cause security vulnerabilities dealing with random-access memory (RAM) access, such as buffer overflows and dangling pointers.
Computer languages such as C and C++ that support arbitrary pointer arithmetic, casting, and deallocation are typically not memory safe. There are several approaches to find errors in such languages: see the Detection section below.
Most high-level programming languages avoid the problem by disallowing pointer arithmetic and casting entirely, and by enforcing tracing garbage collection as the sole memory management scheme.
A language could support even more uses of pointer arithmetic, casting, and deallocation without sacrificing memory safety by using automated theorem proving as a form of static code analysis. ESC/Java with JML demonstrates a way that programmers can declare their invariants in ways that can be understood by a theorem prover.
Types of memory errors
Several types of memory errors can occur, depending on the programming language used:
- Array bounds errors:
- Dynamic memory errors - incorrect management of dynamic memory and pointers:
- Dangling pointer - a pointer storing the address of an object that has been deleted.
- Double free - repeated calls to free may prematurely free a new object at the same address. If the exact address has not been reused, other corruption may occur, especially in allocators that use free lists.
- Invalid free - passing an invalid address to free can corrupt the heap.
- Null pointer accesses will cause an exception or program termination in most environments, but can cause corruption in operating system kernels or systems without memory protection, or when use of the null pointer involves a large or negative offset.
- Uninitialized variables - a variable that has not been assigned a value is used. It may contain an undesired or, in some languages, a corrupt value.
- Wild pointers arise when a pointer is used prior to initialization to some known state. They show the same erratic behaviour as dangling pointers, though they are less likely to stay undetected.
- Out-of-memory errors that do not halt the program can lead to violations of memory safety:
- Stack exhaustion - occurs when a program runs out of stack space, typically because of too deep recursion. A guard page typically halts the program, preventing memory corruption, but functions with large stack frames may bypass the page.
- Heap exhaustion - the program tries to allocate more memory than the amount available. In some languages, this condition must be checked for manually after each allocation.
There are many ways to detect memory errors in programs written in unsafe languages:
- By using special heap allocators that provide dead zones around heap allocated storage and check that accesses don't reach into such dead zones. DieHard and the Allinea Distributed Debugging Tool do this by allocating objects in their own virtual memory page, allowing invalid reads and writes to be stopped and debugged at the exact instruction that causes them.
- Dangling pointers can be trapped using hardware memory protection to stop and debug processes as soon as they try to access memory that has been freed.
- By instrumenting the source code. Tools like SoftBound, CheckPointer and AddressSanitizer do this to collect and track legitimate values for pointers ("metadata") and check each pointer access against the metadata for validity.
- By running the compiled program in a memory-checking virtual machine. The memcheck tool of Valgrind works this way.
- Static code analysis can detect errors in some cases as well.
- By use of an instruction set simulator incorporating memory protection as part of its debugger, such as IBM OLIVER (CICS interactive test/debug)