Hacking

Buffer Overflow — Safe Example, Detection & Fixes

Eng. Donya Bino Published  ·  6 min read
Updated on September 14, 2025

I’m going to walk you through a realistic scenario: you find buggy C code that overflows a buffer. We’ll reproduce the effect (a crash), detect it with tools, and fix it. No exploit payloads, no instructions to gain unauthorized access — only defensive, educational steps.

The story (short)

You’re reviewing code for a small utility that processes user names from input files. It crashes intermittently on production. You suspect memory corruption. Time to reproduce locally, run sanitizers, and patch it.

1) The vulnerable program (harmless demo)

This tiny program intentionally uses an unsafe function so we can observe a failure and learn. It prints and exits — nothing malicious.

/* vuln_name.c — educational demo ONLY */

#include <stdio.h>

#include <string.h>

 

int main(int argc, char **argv) {

    char name[16];

    if (argc < 2) {

        printf("Usage: %s <name>\n", argv[0]);

        return 1;

    }

    /* Unsafe copy: no bounds check */

    strcpy(name, argv[1]);

    printf("Hello, %s\n", name);

    return 0;

}

What happens: if you run ./vuln_name verylongnamethatexceedslimit the program will likely crash or show undefined behavior because strcpy writes past the 16-byte buffer.

2) Reproduce safely (local, controlled)

Do this only on your local machine or an isolated VM designed for testing.

Compile:

gcc -o vuln_name vuln_name.c

./vuln_name shortname        # works

./vuln_name this_name_is_definitely_longer_than_sixteen_bytes

You’ll see unpredictable behavior or a crash. That tells you memory corruption is present — not surprising given strcpy.

3) Use AddressSanitizer to detect the out-of-bounds write

AddressSanitizer (ASan) is a developer tool that detects memory errors at runtime. It’s safe and intended for exactly this use.

Compile with ASan and debug symbols:

gcc -fsanitize=address -g -o vuln_name_asan vuln_name.c

./vuln_name_asan this_name_is_definitely_longer_than_sixteen_bytes

ASan output will clearly show an out-of-bounds write and give you a stack trace pointing to where strcpy was called. That turns a vague crash into actionable information.

4) Static analysis and linters

Run a static analyzer or linter to catch unsafe calls at code-review time:

  1. clang-tidy with security checks
  2.  cppcheck
  3. Commercial tools like Coverity or SonarQube (if available)

These tools will flag uses of strcpy, gets, sprintf, etc., and recommend safer replacements.

5) Fix the bug — safe patterns

There are multiple safe approaches. Pick one that fits your project:

Option A — Bounded copy (minimal change)

/* fixed_name_bounded.c */

#include <stdio.h>

#include <string.h>

 

int main(int argc, char **argv) {

    char name[16];

    if (argc < 2) {

        printf("Usage: %s <name>\n", argv[0]);

        return 1;

    }

    /* copy up to sizeof(name)-1 and ensure null termination */

    strncpy(name, argv[1], sizeof(name) - 1);

    name[sizeof(name) - 1] = '\0';

    printf("Hello, %s\n", name);

    return 0;

}

Note: strncpy has quirks (it doesn’t guarantee null-termination if source too long), so we explicitly set the final '\0'.

Option B — Safer alternative APIs

If your platform supports safer functions (e.g., strlcpy on BSD-like systems), use them:

strlcpy(name, argv[1], sizeof(name));  // copies and null-terminates

(If strlcpy isn’t available, you can implement small wrappers or use libraries that provide safe string handling.)

Option C — Dynamic allocation (for variable-length input)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

int main(int argc, char **argv) {

    if (argc < 2) {

        printf("Usage: %s <name>\n", argv[0]);

        return 1;

    }

    char *name = strdup(argv[1]); // remember to check NULL in production

    if (!name) return 1;

    printf("Hello, %s\n", name);

    free(name);

    return 0;

}

Dynamic allocation removes fixed small buffers but means you must manage memory responsibly (check malloc/strdup return values, free memory, etc.).

6) Re-run ASan and tests

After you apply a fix, compile with ASan again and run the same inputs. ASan should no longer report the out-of-bounds write. Add unit tests to your test suite that assert behavior for long inputs so regressions are caught early.

7) CI and toolchain hardening (prevent future bugs)

Add these checks to your pipeline:

  1. Static analysis on every PR (e.g., clang-tidy, cppcheck).
  2. ASan/UBSan runs on CI for non-production builds to catch runtime issues.
  3. Fuzzing for parsers: run a fuzzer (e.g., AFL, libFuzzer) against input-handling code to uncover edge cases. Fuzzing is defensive — it finds crashes so you can patch them.
  4. Compiler hardening: build dev artifacts with -fstack-protector-strong, -D_FORTIFY_SOURCE=2, and enable warnings as errors where practical.

8) Runtime mitigations (production safety net)

Even with safe code, good runtime settings make exploitation much harder:

  1. ASLR (address space layout randomization) — enabled by default on modern OSes.
  2. DEP/NX — mark data pages non-executable.
  3. Stack canaries — detect stack corruption before returns.
  4. Run services in least privilege mode, containers, or VMs — so a crash or exploit has limited impact.

5.       These are defensive layers — they don’t replace fixing the bug, but they reduce risk.

9) What not to do (important)

  1. Don’t post or reuse exploit shellcode or exploit chains. That’s beyond defensible education and helps attackers.
  2. Don’t test on production systems. Always use local/isolated test environments or explicitly authorized pentest targets.
  3. Avoid glib “fixes” that silently truncate data without logging — if truncation affects behavior, handle it explicitly and inform users.

10) Wrap-up: checklist you can use now

Add static analysis to PR gating.

Run ASan on critical components during CI.

Replace unsafe string APIs (strcpy, gets, sprintf) with bounded/safe alternatives.

Add unit tests for boundary conditions (very long inputs).

Use compiler hardening flags for builds.

Limit privileges and enable OS mitigations in production.

Periodically fuzz parsers and input handlers.

I’ve seen teams patch crashes with duct tape — a quick check that hides symptoms but not root causes. The better route is small, consistent investments in tooling and tests. Spending a few hours adding ASan and some CI checks saves weeks of incident handling later. Make memory-safety a habit, not an emergency.

Professional Services

Explore Our Cybersecurity Services

Our insights are backed by hands-on service delivery. If your business needs professional cybersecurity support, our UK-based specialists are ready to help.

© 2016 – 2026 Red Secure Tech Ltd. Registered in England and Wales — Company No: 15581067