A person finds a lamp and spawns a genie after rubbing it.
The genie says, "I will grant you three wishes. The rules are: You can't wish for death or life, or make people fall in love, and you can't wish for more wishes."
The person asks, "Can I wish for less wishes?"
The genie finds this request silly and asks, "Why would you wish for that?"
The person simply makes their wish, "I wish I have zero wishes."
"Fine!" replies the genie. "Granted!
"You have four billion two hundred and ninty five million, nine hundred and sixty seven thousand two hundred and ninty five wishes left."
Below is an example of an 8-bit integer with the starting value 3. Subtract 1 until you come across an integer underflow.
unsigned int (8-bit)
1
Starting value is 3. Subtracting past 0 wraps around.
Unsigned integers have no sign bit. Every bit is used to store the number. When you subtract 1 from 0, the computer tries to borrow from a bit that does not exist. That borrow wraps around from the top instead, so 0 becomes the largest number the type can hold. The interactive thing above shows this on an 8-bit int. 00000000 minus 1 becomes 11111111, which is 255.
This is what the logic looked like on the server-side.
#include <stdio.h>
#include <stdint.h>
void print_binary(uint32_t num) {
for (int i = 31; i >= 0; i--) {
putchar((num & (1u << i)) ? '1' : '0');
}
putchar('\n');
}
/**
* sets number of wishes to zero
*/
void grant_wish(int *num_wish, int value) {
*num_wish = value;
}
/**
* used to decrement number of wishes after wish has been granted.
*/
void decrement_wish_count(int *num_wish) {
(*num_wish)--;
}
int main() {
uint32_t num_wish = 3;
printf("\nInitalize:\n");
printf("bin: "); print_binary(num_wish);
printf("dec: %u\n", num_wish);
// grant wish if wishes are available
if (num_wish > 0) {
grant_wish(&num_wish, 0);
printf("\nGrant wish:\n");
printf("bin: "); print_binary(num_wish);
printf("dec: %u\n", num_wish);
decrement_wish_count(&num_wish);
printf("\nDecrement wish count:\n");
printf("bin: "); print_binary(num_wish);
printf("dec: %u\n", num_wish);
}
return 0;
}
Output:
Initalize:
bin: 00000000000000000000000000000011
dec: 3
Grant wish:
bin: 00000000000000000000000000000000
dec: 0
Decrement wish count:
bin: 11111111111111111111111111111111
dec: 4294967295
Notice that the num_wish > 0 check only guards grant_wish. It stops a new wish from being granted when there are none left, but it does nothing to stop decrement_wish_count as the check only occurs once, which still runs right after. That is why the underflow happens in the output above. Guarding the wrong function is a common way bugs like this slip past a code review. The check looks like it is protecting the wish count, but it is not protecting the operation that actually breaks it.
A real fix needs to guard the decrement itself, not just the grant.
void decrement_wish_count(int *num_wish) {
if (*num_wish > 0) {
(*num_wish)--;
}
}
In 2015, a real bug in Android's media library worked almost the same way. A field in an MP4 file called covr had its size value decremented without a check, and the value underflowed. That underflowed number was then used to control a buffer copy, and it let an attacker run code on the phone just by sending it a crafted video file. This is known as CVE-2015-3827 [1], part of the "Stagefright" vulnerabilities. Nobody checked if a value could go below zero, and it cost every affected phone its security.