AusJournal — Cyber Lab Series ISSN 2200-1883
CWE-121 · Stack Buffer Overflow

Anatomy of a Buffer Overflow: a line-by-line teardown

A box that can hold only 16 characters, and a loop that never checks whether the box is full. In this article we go through one real function, line by line, and see how this small mistake becomes a serious security bug — and how to fix it.

Dr. Pritam Gajkumar Shah · Cyber Lab Series Severity: High

A function like this one does not look dangerous at first. It compiles without any error, works fine in testing, and runs correctly for months — until one day someone sends a longer piece of text than expected, and things break. Below is the function as it was originally written, and then we go through it line by line to understand what it is actually doing.

token_logger.c
#include <stdio.h>
#include <string.h>

void log_token(char *token) {
    char buffer[16];
    int i = 0;

    while (token[i] != '\0') {
        buffer[i] = token[i];
        i++;
    }
    buffer[i] = '\0';

    printf("Logged token: %s\n", buffer);
}

Line by line

#include <stdio.h> / #include <string.h>

These two lines just bring in some ready-made tools — one for printing text (printf), one for handling strings. Nothing to worry about here, just the basic toolbox.

void log_token(char *token) {

This starts a function called log_token. It takes one input, token — some text given to it. The function does not return anything back.

Watch this line
char buffer[16];

This creates a box named buffer that can hold exactly 16 characters — not one more. Remember this number, 16, because everything below has to respect this limit.

int i = 0;

This is just a counter, starting at 0, to keep track of how far we have copied.

Watch this line
while (token[i] != '\0') {

This loop keeps running until it reaches the end of token (marked by '\0'). But notice — it only checks whether token has ended. It never checks whether buffer is already full. That is the real problem.

buffer[i] = token[i]; i++;

This copies one character from token into buffer, then moves the counter forward by one. Simple copying, nothing fancy.

buffer[i] = '\0';

Once copying is done, this adds an end-marker to buffer, so it becomes a proper, complete piece of text.

printf("Logged token: %s\n", buffer);

This simply prints out whatever is inside buffer.

Verdict

The loop copies every character from token into buffer, without ever checking whether buffer (which holds only 16 characters) is already full. If token is longer than 15 characters, the extra characters spill over into the memory sitting right next to buffer — memory that can even include important information the program needs, like where to jump back to after this function finishes. This is exactly why such bugs feel "random" in production: short inputs never trigger it, so it passes normal testing quietly, and only shows up once someone (by mistake or on purpose) sends a longer piece of text.

Class
Stack buffer overflow
Reference
CWE-121
Trigger
Any token longer than 15 characters
Impact
Memory corruption; potential control-flow hijack if the overflow reaches the return address

What it looks like in memory

Here is the same idea, drawn out. On the left, buffer holds a short token — everything fits, nothing is touched outside the box. On the right, a longer token is copied in without any check, and the extra characters spill past buffer's edge into memory that belongs to something else — in this case, the saved return address the function needs to jump back to safely.

NORMAL COPY — token = "abc123" saved return address buffer[16] a b c 1 2 3 \0 · · · · · · · · · 6 bytes used of 16 — 10 free, untouched OVERFLOW — token = 24-char string saved return address — OVERWRITTEN buffer[16] t o k e n c h a r s . . . . . . . 16 bytes filled — 8 more still coming spills upward, overwrites return address Left: the loop stops naturally at the token's own end — well inside the box. Right: with no capacity check, the loop keeps writing past byte 16, straight into memory the function relies on to know where to return to when it finishes.

The fix

The loop needs one more check — not just "has token ended," but also "is buffer already full."

token_logger.c — corrected
void log_token(char *token) {
    char buffer[16];
    int i = 0;

    while (token[i] != '\0' && i < 15) {
        buffer[i] = token[i];
        i++;
    }
    buffer[i] = '\0';

    printf("Logged token: %s\n", buffer);
}

Why 15, not 16

buffer has 16 boxes in total. But we cannot fill all 16 with actual text — one box must stay empty, kept aside for the end-marker ('\0') that gets added after the loop. So we stop the loop at 15, allowing only 15 characters of real text, leaving the 16th box free for that end-marker. If we had used 16 instead of 15, the loop would fill every box, and then trying to add the end-marker afterward would spill one byte outside the box — same mistake, just smaller.

A shorter way to write the same fix: instead of writing the loop by hand, you can simply write strncpy(buffer, token, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; — this does the same job in two lines, and there is no counter to manage by hand, so less chance of making this mistake again. If cutting off long tokens silently is itself not acceptable for your use-case, then instead check the length of token right at the start, and reject it or handle it properly, rather than silently cutting it short.