Question

Stack Smashing Detected in C: Causes and Fixes

cexceptionstack

Question

I am running my compiled C program (./a.out). It executes for some time and then terminates with an error similar to the following:

*** stack smashing detected ***: ./a.out terminated
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)
Aborted

What can cause this message, and how can I find and correct the problem?

Short Answer

This error usually means your program wrote beyond the bounds of a local variable on the stack, often a fixed-size array. You will learn how stack canaries detect corruption, how to locate the faulty write with compiler warnings and sanitizers, and how to prevent it with bounds-aware code.

Concept

A stack smashing detected message is a runtime safety check reporting likely memory corruption.

In C, a function commonly stores local variables on the stack. For example:

void read_name(void) {
    char name[16];
}

The name array can hold at most 15 visible characters plus the terminating \0 character. If code writes past that boundary, it can overwrite nearby stack data.

Modern compilers can insert a hidden value called a stack canary near vulnerable local variables. Before the function returns, generated code checks whether that value changed. If it changed, the runtime assumes an out-of-bounds write occurred and aborts the program rather than continuing with corrupted control data.

The message does not necessarily identify the line where the bad write happened. The overflow may occur earlier, and it may only be detected when the affected function returns.

Typical causes include:

  • Writing past the end of a local array.
  • Using strcpy, sprintf, or gets with insufficient destination space.
  • Passing an incorrect size to memcpy, snprintf, read, or similar functions.
  • Forgetting space for a string's final null terminator.
  • Using an invalid array index, including a negative index.
  • Incorrectly handling formatted input with scanf.

This matters because out-of-bounds writes cause undefined behavior. A crash is one possible result; silent data corruption or a security vulnerability can also result.

Mental Model

Imagine a function's stack frame as a desk with labelled trays:

  • one tray holds name[16];
  • another holds other local data;
  • a sealed alarm strip, the canary, sits near the edge.

Writing name[20] is like putting papers into a 16-slot tray and continuing onto the desk. The program may appear to work until it checks the alarm strip while leaving the function. If the strip has been disturbed, it stops immediately and reports stack smashing.

The important idea is that the failure is often detected after the mistaken write, not exactly at the write itself.

Take Quiz

Syntax and Examples

A fixed-size C array has a strict valid index range.

char code[8];

Valid indexes are 0 through 7. A C string stored in code needs one byte for \0, so it can contain at most 7 visible characters.

Unsafe copy

#include <string.h>

int main(void) {
    char code[8];
    strcpy(code, "ABCDEFGH");  // Needs 9 bytes including '\0'
    return 0;
}

"ABCDEFGH" has 8 characters, but a C string also needs a terminating null byte. strcpy writes 9 bytes into an 8-byte array.

Safer formatted copy

#include <stdio.h>

int main(void) {
    char code[8];
    const char *source = "ABCDEFGH";

    int written = snprintf(code, sizeof code, "%s", source);

    if (written < 0) {
        return 1;
    }

    if ((size_t)written >= sizeof code) {
        puts("Input was too long and was truncated.");
    }

    printf("%s\n", code);
    return 0;
}

sizeof code evaluates to the capacity of the array in this scope. snprintf writes at most sizeof code - 1 visible characters and terminates the result when the buffer size is greater than zero. Its return value lets the program detect truncation.

For input, specify a maximum field width:

char name[20];

if (scanf("%19s", name) == 1) {
    printf("Hello, %s\n", name);
}

The width is one less than the array capacity so there is room for \0. Prefer fgets when input can contain spaces.

Step by Step Execution

Consider this incorrect example:

#include <string.h>

void set_label(const char *input) {
    char label[5];
    strcpy(label, input);
}

int main(void) {
    set_label("orange");
    return 0;
}

Step by step:

  1. main calls set_label with the string "orange".
  2. set_label creates label, which has room for only 5 bytes.
  3. The string "orange" requires 7 bytes: six letters plus \0.
  4. strcpy copies all 7 bytes because it has no destination-size argument.
  5. The final two writes go beyond label and may overwrite nearby stack memory, including the canary.
  6. When set_label returns, the compiler-generated canary check runs.
  7. If the canary differs from its original value, the process aborts with stack smashing detected.

A corrected version defines a clear truncation policy:

#include <stdio.h>

void set_label(const char *input) {
    char label[5];

    snprintf(label, sizeof label, "%s", input);
    printf("Label: %s\n", label);
}

This prints oran. In a real application, you may instead reject input that does not fit.

Real World Use Cases

Bounds checking is needed whenever data enters a fixed-capacity buffer.

  • Command-line tools: Copying command-line arguments into local arrays.
  • Network services: Reading request headers, packet fields, or usernames supplied by clients.
  • File processing: Parsing lines, CSV fields, or binary file records.
  • Embedded systems: Storing sensor labels and messages in small fixed buffers.
  • Logging: Formatting a message before writing it to a log file.
  • User interfaces: Accepting names, passwords, and form fields with a known maximum length.

In each case, decide what should happen when data is too long: reject it, truncate it safely, allocate enough memory dynamically, or process it in chunks.

Take Quiz

Real Codebase Usage

Real C codebases reduce stack-overflow risks through a combination of design, validation, and tooling.

Validate length before copying

#include <stdbool.h>
#include <string.h>

bool copy_username(char destination[32], const char *source) {
    size_t length = strlen(source);

    if (length >= 32) {
        return false;
    }

    memcpy(destination, source, length + 1);
    return true;
}

The guard clause rejects a value that cannot fit, including its terminator.

Pass capacity with buffers

A function receiving an array parameter cannot automatically know the caller's array size. Pass it explicitly:

#include <stdio.h>

int format_path(char *buffer, size_t capacity, const char *file) {
    int result = snprintf(buffer, capacity, "/tmp/%s", file);

    if (result < 0 || (size_t)result >= capacity) {
        return -1;
    }

    return 0;
}

This pattern makes the buffer size part of the function contract.

Use diagnostic builds

During development, compile with warnings and runtime instrumentation:

gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -g \
    -fsanitize=address,undefined -fno-omit-frame-pointer program.c -o program
./program

AddressSanitizer often reports the invalid write at or close to the source line that caused it. For a debugging build, it is also useful to enable stack protection explicitly:

gcc -g -fstack-protector-strong program.c -o program

Compiler options can differ by compiler and platform, so consult your compiler's documentation when integrating them into a build system.

Common Mistakes

Assuming strncpy always produces a valid string

char destination[4];
strncpy(destination, "hello", sizeof destination);
printf("%s\n", destination);  // Undefined behavior: may not end in '\0'

strncpy does not guarantee null termination when the source is as long as or longer than the limit. Prefer snprintf, or explicitly terminate after a carefully chosen copy.

Using sizeof on an array parameter

void clear(char buffer[]) {
    memset(buffer, 0, sizeof buffer); // Wrong: buffer is a pointer here.
}

Inside the function, buffer has decayed to a pointer, so sizeof buffer is the pointer size, not the caller's array capacity. Pass the size separately.

void clear(char *buffer, size_t capacity) {
    memset(buffer, 0, capacity);
}

Giving scanf no width limit

char name[20];
scanf("%s", name); // Unsafe for long input

Use a width that leaves room for the terminator:

scanf("%19s", name);

Confusing byte count with element count

int values[10];
memcpy(values, source, 10); // Copies 10 bytes, not 10 integers

Use the required byte count:

memcpy(values, source, sizeof values);

Disabling the stack protector instead of fixing the bug

Turning off stack protection may hide the immediate message, but the invalid write and undefined behavior remain. Use the report as a signal to locate and correct the out-of-bounds access.

Comparisons

ApproachWhat it doesMain concernTypical use
strcpy(destination, source)Copies until \0Does not know destination capacityAvoid for untrusted or variable-length input
sprintf(buffer, ...)Formats text into a bufferDoes not know buffer capacityUsually replace with snprintf
snprintf(buffer, size, ...)Formats with a byte limitOutput can be truncated; check return valueBounded text formatting
strncpy(destination, source, n)Copies up to n bytesMay omit \0; can pad unnecessarilyUse only when its exact semantics are intended
fgets(buffer, size, stream)Reads a bounded lineMay retain newline; detects input only up to capacityInteractive and file line input
Stack canaryDetects some stack corruptionDetects after corruption; does not prevent every memory bugCompiler runtime protection
AddressSanitizerDetects many invalid memory accessesDevelopment/testing tool with runtime overheadFinding the source of memory errors

A stack canary is a safety alarm, not a replacement for checking bounds. The best fix is always to ensure every write stays within the intended object.

Take Quiz

Cheat Sheet

  • char text[N]; has valid indexes from 0 to N - 1.
  • A C string needs one extra byte for \0.
  • For an array in the same scope, use sizeof array as its capacity.
  • For a pointer parameter, pass capacity separately: func(char *buffer, size_t capacity).
  • Prefer snprintf(buffer, sizeof buffer, ...) over sprintf.
  • Avoid unbounded strcpy, sprintf, and scanf("%s", buffer).
  • Use scanf("%Ns", buffer) with N = capacity - 1, or prefer fgets.
  • Check snprintf: truncation occurred when its nonnegative return value is >= capacity.
  • Compile debugging builds with -Wall -Wextra -g -fsanitize=address,undefined.
  • A stack-smashing message usually means the corrupted function is being exited; inspect writes made earlier in that function.

FAQ

What does “stack smashing detected” mean in C?

It usually means a stack-protector canary was modified, most often because code wrote outside a local stack array.

Is stack smashing always caused by strcpy?

No. Any out-of-bounds write can cause it, including bad indexes, oversized memcpy calls, unsafe scanf, and incorrect length calculations.

Why does the error occur after my program has been running?

The bad write may happen earlier. The canary is commonly checked when a function returns, so detection can be delayed.

Does the backtrace point to the bug in my code?

Not necessarily. Functions such as __fortify_fail are reporting the failed safety check. Use a debugger or AddressSanitizer to find the original invalid write.

Can I fix the error by increasing the array size?

Only if the larger size is a deliberate, valid limit and all inputs are still checked. Blindly increasing an array can hide a logic error and does not safely handle unlimited input.

Should I disable stack protection?

No. Disabling it removes a diagnostic and protection mechanism, but does not repair the memory corruption.

How can I find the exact line causing the overflow?

Rebuild with debug symbols and AddressSanitizer, run the same failing input, and inspect the sanitizer report. Also enable compiler warnings and review all writes to local arrays.

Take Quiz

Mini Project

Description

Build a small command-line greeting formatter that reads a name into a fixed-size buffer and safely creates a greeting. It demonstrates bounded input, bounded formatting, and explicit handling of input that does not fit.

Goal

Read a name safely and print a greeting without writing beyond any buffer.

Requirements

Use fgets to read the name into a fixed-size character array. Remove the trailing newline when one is present. Reject an input line that is longer than the available input buffer. Format the greeting with snprintf. Detect and report greeting truncation.

Take Quiz

Keep learning

Related questions

View more