Skip to Content
Course content

40: Common String Bugs and Buffer Overflows

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Take a look at this snippet. It’s a simple function meant to store a username for a game profile. At first glance, it looks perfectly reasonable.

char username[16];
char input[100];

printf("Enter your username: ");
scanf("%s", input); 

// Copy the input into our fixed-size profile buffer
strcpy(username, input); 
printf("Welcome, %s!\n", username);

If you run this and type "Alice", it works great. But try typing "ThisIsALongUsernameThatWillDefinitelyCrashTheProgram". The program will likely crash with a Segmentation Fault, or worse, it will keep running while silently corrupting other variables in your memory. This is the classic buffer overflow, and it is arguably the most dangerous mistake you can make in C.

The Invisible Cliff: Writing Past Your Buffer

The problem here is that strcpy is "blind." It doesn't know that username is only 16 bytes long. It just starts copying characters from input to username until it hits a null terminator (\0). If the input is 40 characters long, strcpy will dutifully write all 40 characters into that 16-byte space, stomping right over whatever happened to be stored in the memory addresses immediately following your array.

In a real-world scenario, that adjacent memory might be a return address on the stack. If an attacker knows exactly how much to overflow, they can overwrite that address to point to their own malicious code. This is how some of the most famous security exploits in history started. I've seen this happen in production code more often than I'd like to admit, usually because a developer assumed "the user would never enter more than 16 characters." Never trust the user.

Stopping the Bleed with Bounds Checking

To fix this, we need a way to tell the copy function exactly when to stop. The immediate answer is strncpy. It adds a size argument to ensure we don't exceed the destination buffer.

char username[16];
char input[100];

printf("Enter your username: ");
scanf("%s", input); 

// Only copy up to 15 characters to leave room for the null terminator
strncpy(username, input, sizeof(username) - 1);
username[sizeof(username) - 1] = '\0'; 

printf("Welcome, %s!\n", username);

You'll notice I subtracted one from the size and then manually set the last byte to \0. This is a crucial detail. If strncpy hits the limit before it finds a null terminator in the source string, it simply stops copying—leaving the destination string unterminated. If you try to print an unterminated string, printf will keep reading past your buffer into random memory until it happens to find a zero byte, resulting in "garbage" characters or a crash.

The Modern Approach: snprintf

While strncpy is the standard textbook answer, in my own professional projects, I almost always use snprintf for this. It's slightly more versatile and, more importantly, it guarantees a null terminator at the end of the string, provided the size is greater than zero.

char username[16];
char input[100];

printf("Enter your username: ");
scanf("%s", input); 

// snprintf handles the termination for us automatically
snprintf(username, sizeof(username), "%s", input);

printf("Welcome, %s!\n", username);

This approach is cleaner and less prone to the "off-by-one" errors that haunt strncpy users. It treats the buffer size as a hard limit and ensures the resulting string is always valid C-string. When you're moving strings around, make it a habit: if the function doesn't take a maximum length argument, you probably shouldn't be using it.




📋 Practical Task

Hardening a Log Message Parser

You've been handed a piece of a logging system that is crashing intermittently. The code below takes a raw log message and attempts to extract the "Error Level" (e.g., "INFO", "WARN", "ERROR") into a small buffer for processing. However, it is currently vulnerable to buffer overflows if the log message is malformed.

Your Task: Rewrite the extract_level function to prevent buffer overflows. Ensure that the level buffer is never overwritten beyond its bounds and that the resulting string is always properly null-terminated, even if the input is too long.

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

void extract_level(const char *log_msg) {
    char level[8]; // Buffer for levels like "INFO", "DEBUG", "ERROR"
    
    // BUGGY CODE: This will overflow if the first word of log_msg is > 7 chars
    sscanf(log_msg, "%s", level); 
    
    printf("Log Level Extracted: %s\n", level);
}

int main() {
    // This should work
    extract_level("INFO: System started");
    
    // This will likely crash or corrupt memory
    extract_level("CRITICAL_FAILURE: Kernel panic"); 
    
    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.