Why do segmentation faults occur?

What is segmentation fault (or segfault)?

A segmentation fault occurs when a program attempts to improperly access memory – and there are several ways memory can be accessed improperly. Modern operating systems typically handle these errors and terminate the offending program. Rarely, a segmentation fault can be caused by a hardware error, but for our purposes we will be taking a look at software-based memory access errors. Although, segmentation faults are the result of invalid memory access, incorrect access to memory will not always result in a segmentation fault. Therefore, the consequence of an erroneous memory access may or may not result in a program crashing. Often times, memory access errors can lay dormant without any apparent side-effects for years before they trigger a segmentation fault. This can make detecting and diagnosing memory access errors extremely difficult and time consuming. Below, we’ll take a look at various types of memory access errors and see how they are manifested in code.

Out-of-bounds array access

One of the most common memory access errors is an out-of-bounds (OOB) array access (also referred to as buffer overrun or buffer overflow). Almost all C programmers have at one point tried to access a non-existent element by using an invalid index. Here’s an example of an OOB array access:


                
                
            
Also called an off-by-one error, this code attempts to assign a value to a place in memory that is located just after the end of the array. If the memory after this array is unused, then this error may not result in any runtime error. Conversely, if the memory after the array is used in a meaningful way, data corruption will occur and program execution can be unpredictable. Modern compilers such as GCC can detect and report errors when writing past the end of stack-based arrays.
*** stack smashing detected ***: unknown terminated
However, reading from an invalid array will not result in a GCC error. For example, the code below reads memory from outside the defined array, but GCC will not report the error.

                
                
            
The above are examples of invalid access to stack-based memory, but memory can also be defined on the heap or in the data segment (e.g. global variables). Depending on where the variable is defined, the consequence of an invalid memory access can be subtly different. Corrupting certain parts of memory can even change the instructions the CPU uses to execute instructions. Buffer overflows have famously been exploited countless times by malicious hackers to take over vulnerable systems.

Null (or uninitialized) pointer dereference

A pointer typically contains the memory address of another object or value in memory – it “points” to another location in memory. However, pointers that are uninitialized (not explicitly assigned a value) can have an arbitrary value that might point to valid or invalid memory. Similarly, a pointer that is explicitly initialized to null points to invalid memory. Dereferencing a pointer with an invalid value will result in a segmentation fault. Occasionally, there can be conditions where a null pointer deference can be abused to bypass security mechanisms or exploited to execute arbitrary code. Below is an example of using an uninitialized pointer. Since a is never initialized, scanf() attempts to read input into an invalid memory location and the result is a segmentation fault.


                
                
                
            

Use-after-free

In C/C++, stack allocated memory can be used even if it is out of out-of-scope, and heap allocated memory can be used after it has been deallocated. Consequently, reading from freed (or out-of-scope) memory might yield undefined values or sensitive data. Writing to freed (or out-of-scope) memory might cause a segmentation fault or corrupt data if the memory is recycled and reused. In certain cases, writing malicious data to freed memory can result in arbitrary code execution. More generally, using memory after it has been freed results in unpredictable behavior that is often hard to diagnose. Below is an example of a function that returns a local stack variable. Since h is declared inside the function hex(), it goes out of scope after return is called. Use of the returned value can lead to unpredictable results.


                
                
                
            

Double free

A double free occurs when there is an attempt to deallocate memory after it has already been deallocated (calling free() twice on the same pointer). When free() is called on the same value, it can lead to corruption of memory management data structures (i.e. malloc’s internal data structures). Double frees often happen during error conditions where the code execution path isn’t properly anticipated. Similar to use-after-free bugs, corruption of memory management data structures can lead to arbitrary code execution. In the example below, free() is called twice on the variable y.


                
                
                
            

Constant data corruption

In C/C++, constant data is mapped to a read-only region of memory. Since constant data is read-only, attempting to write data to this region of memory results in a segmentation fault. In the example below, name1 points to the constant string “Franz”. However, the attempt to assign a value to the first character of name1 will result in a segmentation fault