Out-of-bounds write
Crossing boundaries: a guide to safely handling input in memory buffers
~20mins estimatedC++
What is out-of-bounds write?
Out-of-bounds write is a class of vulnerability that exists typically in languages that require the programmer to do their own memory management (such as C and C++). It occurs when a program writes data beyond the boundaries of an allocated memory buffer, causing adjacent memory regions to be overwritten. This often happens when copying user input without validating its length against the buffer size. Buffer overflow is another term commonly used to describe this class of vulnerability.
Depending on the scenario, an out-of-bounds write can lead to variables being overwritten, or in serious cases, lead to hijacking of the control flow of the program if the affected data regions contain pointers or code that is executed, resulting in arbitrary code execution.
About this lesson
In this lesson, you will learn about the out-of-bounds write vulnerability class and how to protect your applications against it by improving memory safety. We will step into the shoes of a game developer named Henry, working with a team to develop a fictional RPG game called DungeonSlash, who abuses an out-of-bounds write vulnerability to win every single office tournament.
Meet Henry, a game developer who worked with a team to develop their latest multiplayer terminal-based RPG game, DungeonSlash. Since it was still in early development, the team decided that one of the best ways to test the game was to play it extensively. Every day for an hour in the afternoon, the office would gather for a tournament, and the winner would receive chocolates.
The game ran on the command line and first registered the player with their class and name. The classes were human, elf, dwarf, or dragon, with different stats such as damage, health, speed, and armor. After registering the player, the rounds began, and each player could start making moves and attacking each other. The last player standing won the game, and their name and class were visible to other players on a scoreboard (but not their stats). Here is how the game started:
Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? elfWhat's your player's name? Celeste---initiating Elf class---Registered player Celeste: elfPlayer stats: damage 12 health 8 speed 15 armor 5What's your first move?Henry really loved chocolate. One day, while reviewing the DungeonSlash code, he discovered a clever way to cheat so that he could win every game. He did this by customizing his player with a mysterious name "the mighty dragon with large clawzz" to gain extraordinary stats. Here is how Henry started the game:
Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? the mighty dragon with large clawzz---initiating Dragon class---Registered player the mighty dragon with large clawzz: dragonPlayer stats: damage 1918987296 health 1663067495 speed 2054644076 armor 122What's your first move?With those astronomical stats compared to other players, Henry won five games in a row. At the end of the week, he showed his team the bug and patched it together with them before the next release, wearing a gleaming smile and holding a bucketload of chocolate.
Let's see how Henry achieved his clever exploit. Like most RPG games, players have classes and stats. The class Player is defined as below:
When the game starts, the player gets asked some information about their name and class, then registers it with the server:
Like any good RPG game, the stats for each player class are carefully balanced on the server side. In this case, each class has its own quirks, but the total stats add up to 40:
Henry noticed that the name and class were represented as char arrays. User input was taken with the get_line function, which copies 80 characters using fgets, no matter how large the destination buffer is. When used with a smaller buffer (such as playerName, which is only 20 characters), this presented an opportunity to overwrite other data in memory.
Henry first tested with a very long player name by entering a bunch of 'A's:
Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? humanWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA---initiating Human class---registered player AAAAAAAAAAAAAAAAAAAAAA: humanplayer stats: damage 10 health 10 speed 10 armor 10Abort trap: 6Interestingly, the game does print out normal stats (since this is a human player, 10 for each), then it crashes with "Abort trap: 6", which is usually an error for writing to memory that the process does not own.
At this point, he could cause a Denial of Service (DoS) and crash the game, which was a valid exploit in itself. However, that didn't exactly help him win. He noticed that the stats are unchanged by his out-of-bounds write because the stats were set in the initiate_player_class appropriately based on the class, after the name and class were copied. Which meant he needed to get clever about it.
The matching of the class was based on the char playerClass[10] variable, which is compared via strcmp for an exact match. That means if the player's class is " dragon " (with a space at either end) instead of "dragon" (with no spaces), it won't match, and the stats won't be initiated at all. This gave Henry an opportunity to customize his own stats based on the name of the player, while having an innocuous class name that does not tip off to his friends that he's cheating.
Henry decides to use a popular technique called "fuzzing" (which means stress testing a program with a variety of inputs) to discover desirable inputs to exploit this vulnerability. He has a couple of requirements for a desirable outcome:
- Really high stats for his player
- Doesn't crash the game
- The name looks somewhat normal (to not tip off his friends)
He wrote this simple bash script fuzz.sh to try inputs of 'A's from size 20 (the size of the playerName buffer) to 80 (value of LINEMAX):
Scrolling through the output, he notices that the first few attempts of input sizes don't really change the stats, but rather cause them to be uninitialized, random but large values:
--------- trying input size 20 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 71145744 health 1 speed 71139328 armor 1What's your first move?--------- trying input size 21 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 77617408 health 1 speed 77611008 armor 1What's your first move?--------- trying input size 22 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 68026368 health 1 speed 68059136 armor 1What's your first move?...Around input size 33-35, the values of stats start being completely overwritten:
--------- trying input size 33 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 1094795585 health 1094795585 speed 4276545 armor 0What's your first move?--------- trying input size 34 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 1094795585 health 1094795585 speed 1094795585 armor 0What's your first move?--------- trying input size 35 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 1094795585 health 1094795585 speed 1094795585 armor 65What's your first move?--------- trying input size 36 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 1094795585 health 1094795585 speed 1094795585 armor 16705What's your first move?--------- trying input size 37 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 1094795585 health 1094795585 speed 1094795585 armor 4276545./fuzz.sh: line 1: 35615 Done echo -e "dragon \n$BUFFER\n" 35616 Abort trap: 6 | ./dungeonslash--------- trying input size 38 --------------Welcome to DungeonSlash! Let's create your player.What's your player's class (human|elf|dwarf|dragon)? dragonWhat's your player's name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA---initiating Dragon class---registered player AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: dragonplayer stats: damage 1094795585 health 1094795585 speed 1094795585 armor 1094795585./fuzz.sh: line 1: 35618 Done echo -e "dragon \n$BUFFER\n" 35619 Abort trap: 6 | ./dungeonslashHe noticed that the number 1094795585 kept repeating in the output, and realized that this was the value 0x41414141 in hexadecimal, or "AAAA" in ASCII, which corresponded to the repeated 'A' characters in his input.
At input size 35, the memory buffer of the Player object looks like this:

As shown, the damage, health and speed stat in memory has all filled up with "AAAA" which is why they have the value 1094795585. The armor is 65 (0x41), a single "A".
At size 36 and 37, the armor value is 16705 (0x4141) and 4276545 (0x414141) respectively, which means it takes exactly 38 "A"s to fill up the entire buffer. However, the game starts crashing at 37 'A's.
Henry decided that to be safe, he would use exactly 35 characters to avoid crashing the game, and craft a name using real words so as not to tip off his colleagues. He knew that damage, health, and speed would be filled by the first 34 characters, and needed to choose a 35th character that maximized his armor value. Consulting the ASCII table, he found that lowercase 'z' had the largest decimal value at 122. So he chose the name "the mighty dragon with large clawzz", exactly 35 characters and plausible enough for a dragon class player, gaining 122 armor along with astronomical values for damage, health, and speed.
The impact of out-of-bounds write
Depending on the application, out-of-bounds write vulnerabilities can have varying impacts, from simple Denial of Service (crashing the program) to authentication bypass (see this Snyk blog post on authentication bypass for an example), and even remote code execution through control flow hijacking.
The vulnerability can typically only be exploited if user input reaches the vulnerable code path. Additionally, certain conditions may limit exploitation possibilities. For example, in DungeonSlash, the class initialization occurred after the get_line call, which would normally overwrite any values corrupted by the overflow. However, a sufficiently large input would still crash the game. Henry had to exploit the fact that class names were not properly validated, tricking the program into skipping the stat initialization and preserving his inflated values.
If the bug had been discovered after the game was released (instead of being patched by the developers), it could have enabled players to crash servers, cheat, or even gain shell access via attacks such as Ret2Libc.
The root cause of this vulnerability was that data exceeding the buffer size was copied and 80 bytes were written into a buffer regardless of its actual capacity. To mitigate this, the get_line function needs to know the size limit of the destination buffer.
Here is the corrected get_line function with proper bounds checking:
The calling code must now pass the appropriate buffer sizes to prevent out of bounds writes:
With these changes, players can no longer overflow name or class data into the stats fields, eliminating both the cheating exploit and the potential for crashing or hijacking the program.
Test your knowledge!
Keep learning
Learn more about out-of-bounds write from the following resources!
- The CWE page matching this vulnerability type
- A popular sub-type of out-of-bounds write, buffer overflow
- Buffer overflow attacks in C++: A hands-on guide