Wednesday, November 2, 2022

Beginning Integer Overflow/Underflow - Signed and Unsigned integers

Still on the journey providing mentorship to the SANS/Ryerson/Rogers Cyber Secure Catalyst Program.

In this post, the ask was to explain integer overflow/underflow. 

Keeping it simple! The basic ideas, in the case of a 32-bit system or code compiled as 32 bits, an integer signed or unsigned, will occupy 32 bits. If we calculate 2**32 we get 4,294,967,296 possible values. In the case of unsigned int, this means, we should be able to get 0 to 4,294,967,295 as the possible values, as unsigned int only allows for positive values. Signed int on the other hand, allows for negative numbers and have a range of −2,147,483,648 to 2,147,483,647. Note, this problem is not only about 32-bit integer the idea is basically applying a value larger to a datatype than it was designed to accommodate. Int is just the example used in this case but it could have been something else.

Let's define a small program to learn more:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int main()
    {	  
	  // Define a variable
        unsigned int my_num = 429496725;

        // Get the size of an integer for this compiled code
        printf("Size of my_num is :%d bytes or %d bits \n", sizeof(my_num), sizeof(my_num) * 8);

        // Print the value of my number to the screen
        printf("Current value for my_num is: %u \n", my_num);
        
        return 0;
    }

Let's compile and run this small program

1
2
3
4
┌──(kalisecuritynik)-[~]
└─$ gcc intOverflow.c -o intOverflow -m32 && ./intOverflow 
Size of my_num is :4 bytes or 32 bits 
Current value for my_num is: 429496725 

Above we see the size of the my_num integer is 4 bytes or 32 bits. We also see the current value is set to the maximum value possible for a 32-bit unsigned int. 

What would happen if we add 1 to this program. 

Here is the updated code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int main()
    {
	  // Define a variable
        int  my_num = 4294967295;

        // Get the size of an integer for this compiled code
        printf("Size of my_num is :%d bytes or %d bits \n", sizeof(my_num), sizeof(my_num) * 8);

        // Print the value of my number to the screen
        printf("%u + 1 = %u \n", my_num, my_num + 1);
        
        return 0;
    }

When compiled and run, we get:

1
2
3
4
┌──(kalisecuritynik)-[~]
└─$ gcc intOverflow.c -o intOverflow -m32 && ./intOverflow 
Size of my_num is :4 bytes or 32 bits 
4294967295 + 1 = 0 

Realistically, that value should have been 4294967295 + 1 = 4294967296.

It looks like we hit the overflow.

What would happen if we update the code to add 10. Here we see:

1
2
3
4
┌──(kalisecuritynik)-[~]
└─$ gcc intOverflow.c -o intOverflow -m32 && ./intOverflow 
Size of my_num is :4 bytes or 32 bits 
4294967295 + 10 = 9 

From above, we see we are wrapping around. Previously when we went over by 1 the number did 1-1 which gave a result of 0. When we went over by 10, our answer is 9. 

Anyhow that's it for the unsigned int overflow.


Signed int

As discussed above, in both the signed and unsigned int perspective, the size is 4 bytes or 32 bits. The difference between signed and unsigned, is that signed can accommodate negative numbers. While the range for unsigned is 0 to 4,294,967,295 signed values are from −2,147,483,648 to 2,147,483,647. Let's take the max and - 0 from it.

Here is the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int main()
    {
   // Define a signed integer. Notice the "-"
        signed int  my_num_signed = -2147483648;

        // Get the size of an signed integer for this compiled code
        printf("Size of my_num_signed is :%d bytes or %d bits \n", sizeof(my_num_signed), sizeof(my_num_signed) * 8);

        // Print the value of my number to the screen
        printf("%d - 0 = %d \n", my_num_signed, my_num_signed -0);
        
        return 0;
    }

When compiled, here is what we have:

1
2
3
4
┌──(kalisecuritynik)-[~]
└─$ gcc intOverflow.c -o intOverflow -m32 && ./intOverflow 
Size of my_num_signed is :4 bytes or 32 bits 
-2147483648 - 0 = -2147483648

The above is expected as -2147483648 - 0 = -2147483648.

Let's modify the code to -1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>

int main()
    {
        signed int  my_num_signed = -2147483648;
        // Get the size of an signed integer for this compiled code
        printf("Size of my_num_signed is :%d bytes or %d bits \n", sizeof(my_num_signed), sizeof(my_num_signed) * 8);

        // Print the value of my number to the screen
        printf("%d - 1 = %d \n", my_num_signed, my_num_signed -1);
        
        return 0;
    }

When this is compiled we see 

1
2
3
4
┌──(kalisecuritynik)-[~]
└─$ gcc intOverflow.c -o intOverflow -m32 && ./intOverflow 
Size of my_num_signed is :4 bytes or 32 bits 
-2147483648 - 1 = 2147483647

Oh! oh! oh! oh! What we expect is that -2147483648 -1 should equal -2,147,483,649. Therefore, above resulted in an underflow.

Similarly, if we -10 we see:

1
2
3
4
┌──(kalisecuritynik)-[~]
└─$ gcc intOverflow.c -o intOverflow -m32 && ./intOverflow 
Size of my_num_signed is :4 bytes or 32 bits 
-2147483648 - 10 = 2147483638

Let's wrap up with one more. This time, taking the max number on the positive side of the signed values.

Here is the code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>

int main()
    {
        signed int  my_num_signed = 2147483647;
        // Get the size of an signed integer for this compiled code
        printf("Size of my_num_signed is :%d bytes or %d bits \n", sizeof(my_num_signed), sizeof(my_num_signed) * 8);

        // Print the value of my number to the screen
        printf("%d + 1 = %d \n", my_num_signed, my_num_signed + 1);
        
        return 0;
    }

When compiled and run we see ...

1
2
3
4
┌──(kalisecuritynik)-[~]
└─$ gcc intOverflow.c -o intOverflow -m32 && ./intOverflow 
Size of my_num_signed is :4 bytes or 32 bits 
2147483647 + 1 = -2147483648