Chapter 21.3 — Alternate Signal Stack: sigaltstack()
Handling SIGSEGV When the Stack Has Overflowed | EmbeddedPathashala
21.3
Section
3
Code Examples
6
Interview Qs
Key Terms
sigaltstack() SA_ONSTACK stack_t ss_sp ss_size SIGSTKSZ MINSIGSTKSZ SS_ONSTACK SS_DISABLE SIGSEGV Stack Overflow
The Problem: Stack Overflow + SIGSEGV
When a signal handler is invoked, the kernel creates a new stack frame for it on the process’s standard stack. This works fine normally. But there’s a specific case where it fails completely:
What happens when the stack overflows:
Process runs
↓
Infinite recursion / large locals
↓
Stack space exhausted
↓
Kernel sends SIGSEGV
↓
Handler can’t run — no stack space!
Process killed silently
Process killed silently
With sigaltstack()
↓
Alternate stack pre-allocated on heap
↓
SIGSEGV handler registered with SA_ONSTACK
↓
Handler runs on alternate stack — cleanup, log, exit gracefully
The sigaltstack() API
#include <signal.h>
/*
* sigaltstack() — establish or query an alternate signal stack
*
* sigstack: pointer to stack_t describing the NEW alternate stack
* (can be NULL to just query)
* old_sigstack: filled with info about the PREVIOUS alternate stack
* (can be NULL to discard)
*
* Returns 0 on success, -1 on error.
*/
int sigaltstack(const stack_t *sigstack, stack_t *old_sigstack);
/*
* The stack_t structure:
*/
typedef struct {
void *ss_sp; /* Starting address of the alternate stack buffer */
int ss_flags; /* SS_ONSTACK: currently on alt stack
SS_DISABLE: disable the alt stack */
size_t ss_size; /* Size of the alternate stack in bytes */
} stack_t;
/*
* Size constants:
*
* SIGSTKSZ — recommended typical size (8192 bytes on Linux/x86)
* MINSIGSTKSZ — absolute minimum size to invoke a handler (2048 bytes)
*
* Always use SIGSTKSZ or larger. Never use less than MINSIGSTKSZ.
*/
Three Steps to Use sigaltstack():
1. Allocate a memory buffer (on heap with malloc, or statically)
2. Call
3. Set
1. Allocate a memory buffer (on heap with malloc, or statically)
2. Call
sigaltstack() to register it with the kernel3. Set
SA_ONSTACK flag when registering your signal handlerCode Example 1 — Catching Stack Overflow with sigaltstack()
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
static void sigsegv_handler(int sig)
{
/* UNSAFE: printf used here for demonstration only */
printf("\n[SIGSEGV Handler] Caught signal %d\n", sig);
printf("[SIGSEGV Handler] Stack overflow detected and handled!\n");
printf("[SIGSEGV Handler] Calling _exit() safely.\n");
fflush(stdout);
/*
* Must use _exit() here — we cannot safely return to the
* overflowed stack. Never use exit() (not async-signal-safe).
*/
_exit(EXIT_FAILURE);
}
/* A function that recursively overflows the stack */
static void overflow_stack(int depth)
{
char big_array[10000]; /* large local — quickly exhausts stack */
big_array[0] = depth; /* prevent optimizer from removing it */
if (depth % 100 == 0)
printf(" Depth: %d\n", depth);
overflow_stack(depth + 1); /* infinite recursion */
}
int main(void)
{
/* Step 1: Allocate the alternate signal stack */
stack_t alt_stack;
alt_stack.ss_sp = malloc(SIGSTKSZ);
if (alt_stack.ss_sp == NULL) {
perror("malloc");
return 1;
}
alt_stack.ss_size = SIGSTKSZ;
alt_stack.ss_flags = 0;
/* Step 2: Register the alternate stack with the kernel */
if (sigaltstack(&alt_stack, NULL) == -1) {
perror("sigaltstack");
return 1;
}
printf("[Main] Alternate signal stack at %p (size=%zu)\n",
alt_stack.ss_sp, alt_stack.ss_size);
/* Step 3: Install SIGSEGV handler with SA_ONSTACK flag */
struct sigaction sa;
sa.sa_handler = sigsegv_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK; /* <-- Use the alternate stack for this handler */
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("[Main] Starting infinite recursion to overflow stack...\n");
overflow_stack(1);
/* Never reached */
free(alt_stack.ss_sp);
return 0;
}
Compile & Run:
You’ll see the depth counter growing, then the SIGSEGV handler fires on the alternate stack and prints its message cleanly — instead of a silent crash.
gcc -Wall -o altstack_demo altstack_demo.c && ./altstack_demoYou’ll see the depth counter growing, then the SIGSEGV handler fires on the alternate stack and prints its message cleanly — instead of a silent crash.
Code Example 2 — Static Alternate Stack (No malloc)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
/*
* For embedded or safety-critical code where malloc is not used,
* declare the alternate stack as a static array.
*/
static char alt_stack_buf[SIGSTKSZ];
static void sigsegv_handler(int sig)
{
const char msg[] = "[Handler] SIGSEGV caught on static alternate stack.\n";
write(STDERR_FILENO, msg, sizeof(msg) - 1);
_exit(EXIT_FAILURE);
}
int main(void)
{
/* Use static buffer as alternate stack */
stack_t ss;
ss.ss_sp = alt_stack_buf;
ss.ss_size = sizeof(alt_stack_buf);
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) == -1) {
perror("sigaltstack");
return 1;
}
struct sigaction sa;
sa.sa_handler = sigsegv_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
sigaction(SIGSEGV, &sa, NULL);
printf("[Main] Alt stack setup complete. Triggering SIGSEGV...\n");
/* Dereference NULL to trigger SIGSEGV */
volatile int *bad_ptr = NULL;
*bad_ptr = 1;
return 0;
}
Code Example 3 — Querying and Disabling the Alternate Stack
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void print_altstack_info(const char *label)
{
stack_t old_ss;
if (sigaltstack(NULL, &old_ss) == -1) {
perror("sigaltstack query");
return;
}
printf("[%s]\n", label);
printf(" ss_sp = %p\n", old_ss.ss_sp);
printf(" ss_size = %zu bytes\n", old_ss.ss_size);
printf(" ss_flags = %s%s\n",
(old_ss.ss_flags & SS_ONSTACK) ? "SS_ONSTACK " : "",
(old_ss.ss_flags & SS_DISABLE) ? "SS_DISABLE" : "");
if (old_ss.ss_flags == SS_DISABLE)
printf(" (No alternate stack currently established)\n");
}
int main(void)
{
/* Before setup: no alternate stack */
print_altstack_info("Before sigaltstack()");
/* Allocate and install alternate stack */
void *buf = malloc(SIGSTKSZ);
stack_t ss = { .ss_sp = buf, .ss_size = SIGSTKSZ, .ss_flags = 0 };
sigaltstack(&ss, NULL);
/* After setup */
print_altstack_info("After sigaltstack()");
/* Disable the alternate stack */
stack_t disable_ss = { .ss_sp = NULL, .ss_size = 0, .ss_flags = SS_DISABLE };
sigaltstack(&disable_ss, NULL);
/* After disable */
print_altstack_info("After SS_DISABLE");
free(buf);
return 0;
}
SS_ONSTACK in returned flags: When you call
sigaltstack(NULL, &old) from within a signal handler running on the alternate stack, old.ss_flags will have SS_ONSTACK set, indicating you are currently running on the alternate stack. You cannot replace the alternate stack while it is in use (results in EPERM).Interview Questions — sigaltstack()
Q1. Why does the kernel’s SIGSEGV handler fail to run when the stack overflows?
When SIGSEGV is generated due to stack overflow, the stack space is already exhausted. The kernel normally creates the signal handler frame on the process’s standard stack — but there is no space. So the handler cannot be set up, and the default action (process termination) occurs instead.
Q2. What are the three steps to correctly set up sigaltstack()?
(1) Allocate a memory buffer for the alternate stack — either with
malloc(SIGSTKSZ) or a static array. (2) Call sigaltstack() to register the buffer with the kernel. (3) When installing the signal handler with sigaction(), include the SA_ONSTACK flag so the kernel uses the alternate stack for that handler’s frame.Q3. What is SIGSTKSZ and MINSIGSTKSZ?
SIGSTKSZ is the recommended typical size for an alternate signal stack (8192 bytes on Linux/x86). MINSIGSTKSZ is the absolute minimum size required to invoke a signal handler (2048 bytes on Linux/x86). You should always use at least SIGSTKSZ to avoid overflowing the alternate stack itself.Q4. What does SS_ONSTACK mean when returned by sigaltstack()?
When querying the current alternate stack (by passing NULL as the first argument), if the returned
ss_flags has SS_ONSTACK set, it means the process is currently executing on the alternate stack (i.e., inside a signal handler that was dispatched to the alternate stack). Attempting to install a new alternate stack while SS_ONSTACK is set results in an EPERM error.Q5. Does sigaltstack() work automatically for all signal handlers?
No. Installing an alternate stack with
sigaltstack() does nothing by itself for any handler. Each individual signal handler must be registered with the SA_ONSTACK flag via sigaction() to direct its stack frame to the alternate stack. Without SA_ONSTACK, the handler still uses the standard stack regardless of whether an alternate stack is set up.Q6. What happens if the alternate stack itself overflows?
The kernel does NOT resize the alternate stack. If the signal handler uses more memory than the alternate stack can provide (e.g., by calling many nested functions with large local variables), the overflow writes past the end of the buffer, corrupting adjacent memory. This is why the handler on the alternate stack should be simple and minimal — typically just log, cleanup, and call
_exit().Next: SA_SIGINFO — Getting Detailed Signal Information →
