Different defenses interrupt different steps.
It is tempting to call every protection “overflow prevention.” That hides the important distinction: some controls stop an invalid write, some detect corruption, some block data execution, and others make locations harder to predict.
This optional defenses model becomes interactive with JavaScript. The lesson, explanations and checkpoint answer remain readable without it.
- Bounds validation
- Reject a copy that cannot fit before writing. This repairs the operation shown in the model.
- Stack canary
- Check a guard value before an instrumented function returns. It can detect overwrites crossing that guard, but it does not undo corruption or cover every possible write.
- NX / non-executable data
- Disallow fetching instructions from a data mapping. It does not stop a bad write or all code-reuse attacks.
- ASLR with suitable binary support
- Randomize selected memory locations so fixed-address assumptions become less reliable. Coverage depends on platform and configuration.
Other controls, including control-flow integrity and protected return-address mechanisms on supported platforms, target additional transitions. Support and coverage vary. No checkbox here is a claim to emulate a hardened operating system.
Review the length contract, not just the function name.
Trace an input from source to destination. Where does its length come from? Is the destination capacity still available at the call site? Does the copy include a terminator? Is the length signed, truncated or affected by arithmetic overflow?
strcpy,strcat,sprintfandvsprintfneed a proven destination-size contract; their ordinary signatures do not provide a capacity argument.getscannot be given a destination bound and was removed from C11. Do not use it.- For string conversions,
scanffield widths must match the actual destination, including room for the terminator where required. - A manual
getc/fgetc/getcharloop needs a capacity check before each write. A delimiter is not a size limit. strncpyis not a drop-in safety spell: it can omit termination when the source reaches the bound and pads shorter copies.snprintfis size-aware, but callers must check its result and handle truncation.memcpyis only safe when its count and source/destination ranges are valid.
Search tools help locate risky operations, but an API-name match is a review lead, not proof of a vulnerability. Include hand-written loops, integer calculations and trusted-input assumptions in the review.
A tiny fix with an explicit contract.
This helper copies a known readable byte span into a sixteen-byte C-string destination. It rejects oversized input before writing. Callers must supply a valid source span of at least length bytes and must not pass overlapping storage. Embedded zero bytes are allowed, but a string consumer will only see the prefix before the first zero.
#include <stddef.h>
#include <string.h>
int copy_label(char destination[static 16],
const char *source, size_t length) {
if (source == NULL || length >= 16) {
return -1; /* reject before touching destination */
}
memcpy(destination, source, length);
destination[length] = '\0';
return 0;
}The static 16 parameter expresses a caller precondition, not a runtime capacity check. The caller must really provide that storage. A general-purpose API should receive the destination capacity too. Do not compute sizeof(destination) inside this helper and expect sixteen: an array parameter is adjusted to a pointer.
A mini lab you can actually finish.
- Predict the outputs for lengths 0, 15, 16 and 24. The first two must succeed and terminate inside the destination; the others must fail without a copy.
- In the overflow model, compare fifteen characters with sixteen. Explain why the first illegal write is a zero byte.
- Step through CALL and RET. Identify the slot RET consumes, separately from the function’s integer result.
- Turn on NX in the historical landing model. Explain why blocked data execution is not the same as a repaired copy.
- Write a regression test for the bounded helper: verify the accepted bytes, terminator, return status and unchanged destination on rejection.
For your own local C test harness, compile with warnings, debug information and AddressSanitizer. Keep normal system protections enabled. Use a disposable lab and synthetic inputs, not a production service or privileged binary.
clang -std=c11 -Wall -Wextra -Wpedantic -g -O1 \
-fsanitize=address -fno-omit-frame-pointer label_test.c -o label_test
./label_testlabel_test.c is the test harness you write around the helper, not a hidden download from this site. AddressSanitizer reports many memory violations in exercised code paths; a clean run is not proof that untested paths are safe. The model’s displayed outcomes are illustrative and are not pasted sanitizer output.
What to carry into your next CTF or review.
Start with the actual binary and architecture. Ask what write violates which object, which neighboring bytes are reachable, which control-flow step uses them, and which protections apply. Preserve evidence for each claim. Do not disable protections just to make a historical story appear to work on a current machine.
For production builds, consult your toolchain’s supported hardening options. GCC documents stack protectors and the platform-specific -fhardened bundle; exact availability and contents vary by release. Hardening complements memory-safe design and testing; it is not a reason to leave a known out-of-bounds write unfixed.
If you found this through a code review, revisit the evidence ladder or explore our source code review approach. The strongest outcome is not memorizing an old payload. It is being able to explain the boundary and prove that your fix holds.
Your turn. Call the outcome.
NX blocks an attempted jump into injected stack data. What can you conclude?
Read the explanation
A mitigation can break one exploitation step without repairing the underlying memory-safety violation.
Sources & what we changed
Based on the supplied text of Aleph One’s Smashing the Stack for Fun and Profit, Phrack 49, file 14, 8 November 1996. Original author credit remains with Aleph One. This independent OverWatch Labs explanation uses new prose, synthetic models and modern defensive notes; it is not endorsed by Phrack or the original author.
This lesson maps to: Finding Buffer Overflows; modern defensive additions and corrections by OverWatch Labs. Historical examples are described, not represented as current product vulnerabilities. We correct byte-addressability, array padding, terminator counting and the supplied return-address arithmetic rather than reproducing those errors.
- Aleph One, Smashing the Stack for Fun and Profit, Phrack 49 (1996)
- MITRE CWE-121: Stack-based Buffer Overflow
- GCC: instrumentation and stack-protection options
- Clang: AddressSanitizer
- GDB: examining memory
- Linux kernel: address-space randomization
Original shell-spawning byte arrays and platform-specific exploit drivers are replaced with symbolic models. The causal concepts are preserved; browser outcomes are not native exploitation evidence. Modern controls are editorial additions, not claims made by the 1996 paper.