Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Assigning numerical values to variables is such a common thing in programming that we never ever consider the possibility that these simple operations could sometimes result in very serious security flaws. Today in this post I’ll be throwing some light on this topic and try to explain how a tiny oversight in code could result in a problem which open the doors for some major security vulnerabilities.
Ever heard of “The Heartbleed Bug“? I guess you have. It was a major security vulnerability in the popular OpenSSL cryptographic software library that induced both excitement and fear in the hacker/dev community during its time. It was caused by an arithmetic overflow that allowed an attacker to read sensitive information, such as private keys, usernames, passwords, and other confidential data from memory.
Talking in a layman term, it simply means a special wraparound condition in which the result of an integer subtraction operation instead of reducing the value of an integer, actually increases its value. So basically it does exactly the opposite of what a programmer had intended.
Technically speaking: Integer underflow occurs when an arithmetic operation attempts to produce a numeric value lower than the minimum limit of the data type, causing the value to wrap around to the maximum possible value.
It can also be understood by an example of a car’s analog odometer, which flips back to the maximum number when it crosses zero in reverse.
Look at the following ‘C’ code snippet, here we are declaring an unsigned integer ‘x’ and then assigning ‘0’ to it. Later we are subtracting ‘1’ from this number, which in general mathematical sense would result in ‘-1’, but because computers use bits to store the information it actually results in ‘4294967295’ (a 32 bit unsigned integer), causing an integer underflow bug.
#include <stdio.h>
int main(){
unsigned int x = 0;
x = x - 1; // This leads to underflow and x becomes 4294967295 (for 32-bit unsigned integers)
printf("%d", x); // OUTPUT: 4294967295
}
Let’s assume this code represents ‘magic’ in an RPG game’s inventory system, and each time the player uses his ‘magic’ ability we reduce it by ‘1’, now after it reaches ‘0’ this automatically recharges the players magic with 4294967295.
A better approach to deal with this is as below:
#include <stdio.h>
int main(){
unsigned int x = 0;
/** Some game logic here */
if(x != 0){
x -= 1; // This will prevent the integer underflow bug.
}
printf("%d", x); // OUTPUT: 0
}
You must be thinking, ‘Okay it created some problem but where is the vulnerability?’. If a player gets a huge number of magic power ability that doesn’t amount to a serious security flaw, but just a small programming oversight, right? So lets discuss the vulnerability in our next section.
Lets assume we have a vulnerable application that copies data to a target location from a given source. For this copy operation to complete securely we perform an input size check and allow the copy operation only when the size is within limit. Lets see how we can violate this security and still perform the copy.
#include <iostream>
void copyData(char* dest, const char* src, unsigned int len) {
if (len > 255) {
std::cout << "Error: Data too large to copy." << std::endl;
return; // Prevent copying if the data is too large
}
for (unsigned int i = 0; i < len; ++i) {
dest[i] = src[i];
}
std::cout << "Data copied successfully!" << std::endl;
}
int main() {
char buffer[256]; // 256-byte buffer
const char* data = "This is a test data string."; // Sample data
unsigned int length;
std::cout << "Enter length of data to copy: ";
std::cin >> length;
copyData(buffer, data, length);
return 0;
}
Looking at the code above might not make the problem obvious in the first glance, but as we dig a little deeper we see that the problem lies in the ‘if’ check where we are performing only upper-bound check. So if the user provides a negative number (like -1
), the unsigned int length
will wrap-around to a large positive number (in this case, 4294967295
for a 32-bit system). This bypasses the if (len > 255)
check since 4294967295
is technically greater than 255
, as unsigned int
treats negative input values as large positive numbers.
“Never trust a user’s input and always code defensively”, This is the mantra that we must always stick to. Our application is making an assumption that a safe value will always be passed to it, when in fact, it is not guaranteed. In a real word scenario an attacker could exploit this vulnerability in our application to overwrite important memory regions, crash the program or even worse execute arbitrary code remotely, also known as an RCE attack.
There is no fool proof way to completely mitigate such bugs, its just that we have to be more attentive while writing security checks and a little mindful of how the logic that we are coding could be violated by an attacker. However, there are some pointers that are worth mentioning here:
Following is the secured version of the IF condition check:
if (len < 0 || len > 255) { // Prevent underflow and overflow
std::cout << "Error: Invalid data length." << std::endl;
return;
}
There is another related topic with arithmetic-underflow which is arithmetic-overflow, this is exactly the same concept but just in reverse. Here instead of an underflow an overflow happens and big values wraparound to become small values. However both these conditions can be a significant security risk if not handled properly. Input validation, bounds checking, and using appropriate data types are critical to prevent such vulnerabilities.