POSIX Signals - SIGSEGV
SIGSEGV (Segmentation Violation, signal number 11) is a standard POSIX hardware-generated signal sent by the operating system kernel to a process when it attempts to access memory in a manner that violates memory management or protection constraints.
While SIGBUS indicates that the target memory address is physically invalid or unaligned, SIGSEGV indicates a logical or permission-level memory violation: the memory address exists within the CPU's architecture, but the process has no legal right to access it as requested.
Primary Causes of SIGSEGV
┌────────────────────────────────────────────────────────┐
│ CPU executes memory instruction (load / store / jump) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Memory Management Unit (MMU) checks Page Tables │
└──────────────────────────┬─────────────────────────────┘
│
┌─────────────┴─────────────┐
│ Address unmapped? │ Permissions violated?
▼ ▼
┌──────────────────────────┐┌────────────────────────────┐
│ SEGV_MAPERR ││ SEGV_ACCERR │
│ Address not in any ││ Wrote to read-only page │
│ valid virtual memory map ││ (text segment, string lit) │
│ (e.g. NULL pointer) ││ Executed non-exec page │
└────────────┬─────────────┘└─────────────┬──────────────┘
│ │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Kernel generates SIGSEGV and sends to offending thread │
│ Default Action: Terminate process and dump core │
└────────────────────────────────────────────────────────┘
1. Address Not Mapped (SEGV_MAPERR)
The process attempted to read or write to a memory address that does not belong to any mapped memory region (VMA):
- NULL Pointer Dereference: Attempting to read or write through a pointer with value
0x0(or offset0x0 + struct_member_offset). - Wild / Dangling Pointers: Dereferencing uninitialized pointer variables containing random garbage addresses.
- Use-After-Free: Accessing a heap buffer that has already been returned to the system via
free(), after the underlying virtual pages have been returned viamunmap().
2. Permission Violation (SEGV_ACCERR)
The virtual address is validly mapped, but the access type contradicts the page's memory permissions:
- Writing to Read-Only Memory: Attempting to mutate string literals (e.g.
char *s = "hello"; s[0] = 'H';) or constants residing in the.rodataor.textELF segments. - Instruction Execution on Non-Executable Pages: Jumping to code located on the stack or heap when the page table lacks the executable permission bit (enforced by the CPU's NX / No-Execute bit).
3. Stack Overflow
When a thread exhausts its allocated call stack (often through infinite recursion or allocating massive local arrays char buf[100 * 1024 * 1024]), the stack pointer crosses into an unmapped guard page placed immediately below the stack by the kernel.
Inspecting SIGSEGV Details: siginfo_t
By registering a signal handler with the SA_SIGINFO flag, an application can inspect the exact faulting memory address:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void segv_handler(int sig, siginfo_t *info, void *ucontext) {
const char *reason = "Unknown";
if (info->si_code == SEGV_MAPERR) {
reason = "Address not mapped to object (SEGV_MAPERR)";
} else if (info->si_code == SEGV_ACCERR) {
reason = "Invalid permissions for mapped object (SEGV_ACCERR)";
}
fprintf(stderr, "\nFATAL: Caught SIGSEGV at memory address: %p\n", info->si_addr);
fprintf(stderr, "Reason: %s\n", reason);
// Terminate immediately; resuming would re-execute the faulting instruction
_exit(EXIT_FAILURE);
}
int main() {
struct sigaction sa = {
.sa_sigaction = segv_handler,
.sa_flags = SA_SIGINFO,
};
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
// Trigger SEGV_MAPERR (NULL pointer dereference)
int *bad_ptr = NULL;
*bad_ptr = 42;
return 0;
}
Handling Stack Overflows: sigaltstack
When a thread encounters a stack overflow, the stack pointer (RSP/ESP) is already pointing past the valid stack boundary into unmapped memory.
If the kernel attempts to push a signal frame onto the exhausted stack to run your SIGSEGV handler, the push itself causes another memory fault, instantly crashing the process without executing your handler.
To catch stack overflows, you must configure an Alternate Signal Stack using sigaltstack() and the SA_ONSTACK flag:
#include <signal.h>
#include <stdlib.h>
// Allocate dedicated memory for emergency signal handling
stack_t ss = {
.ss_sp = malloc(SIGSTKSZ),
.ss_size = SIGSTKSZ,
.ss_flags = 0,
};
sigaltstack(&ss, NULL);
struct sigaction sa = {
.sa_sigaction = segv_handler,
.sa_flags = SA_SIGINFO | SA_ONSTACK, // Tells kernel to use alternate stack
};
sigaction(SIGSEGV, &sa, NULL);
Comparison: SIGSEGV vs. SIGBUS
| Characteristic | SIGSEGV (Signal 11) |
SIGBUS (Signal 7) |
|---|---|---|
| Meaning | Segmentation violation / invalid permissions | Bus error / physical or alignment fault |
| Address Legitimacy | Address is logically invalid or unauthorized | Address cannot be physically completed by hardware |
| Typical Cause 1 | NULL pointer dereference | Unaligned memory access (on ARM/SPARC) |
| Typical Cause 2 | Writing to read-only string literal | Dereferencing an mmap() file that was truncated |
si_code |
SEGV_MAPERR, SEGV_ACCERR |
BUS_ADRALN, BUS_ADRERR, BUS_OBJERR |
Frequently Asked Questions (FAQ)
What is the difference between SEGV_MAPERR and SEGV_ACCERR?
SEGV_MAPERR indicates an access attempt to a virtual address that has no mapped page at all (such as a NULL pointer), while SEGV_ACCERR indicates an access to an existing mapped page with invalid permissions (such as writing to read-only memory). The kernel delivers this distinction in the si_code field of siginfo_t.
Why can you not simply catch SIGSEGV and continue execution?
Because SIGSEGV is a synchronous hardware fault generated by a specific CPU instruction; if the handler returns, the CPU immediately retries the exact same instruction that faulted, triggering an infinite fault loop. A SIGSEGV handler should only log diagnostic information (e.g., generate a crash report or stack trace) and exit via _exit() or restore the signal to SIG_DFL and re-raise it.
Why does a stack overflow bypass a normal signal handler?
Because a stack overflow leaves the CPU stack pointer inside an unmapped guard page, preventing the kernel from allocating a stack frame to execute the handler. To catch stack overflows, an application must pre-allocate an emergency stack using sigaltstack() and mark the signal action with SA_ONSTACK.
Related Topics
- POSIX Signals - SIGBUS: Physical memory mapping faults and alignment errors.
- POSIX Signals Architecture & sigaction: Signal delivery, masks, and async-signal safety.
- POSIX Signals - SIGSYS: Bad or restricted system call traps.
