Integer Security

An integer is a whole number, that can be either be positive or negative. In this article, we’re going to be looking at some common security issues when dealing with integers in the C programming language.


Dividing By Zero

Dividing an integer by zero is undefined behavior in C. Attempting such an operation can lead to program crashes. For instance, in the following code we’re taking user supplied input and using this to divide an existing value.

#include <stdio.h>

int main() {
    int a = 100;
    int b = 0;

    printf("Initial value: %d\n", a);
    printf("Enter a number to divide by: ");
    scanf("%d", &b);

    int result = a / b;  // Potential division by zero

    printf("Result: %d\n", result);
    return 0;
}

Entering a positive number and the program works fine.

./divide 
Initial value: 100
Enter a number to divide by: 5
Result: 20

Entering a zero value causes the program to crash.

./divide 
Initial value: 100
Enter a number to divide by: 0
Floating point exception (core dumped)

Integer Overflows

An integer overflow occurs when a calculation performed on an integer causes it to exceed the amount of data allocated to store it.

Integers are stored in fixed-size variables which have a maximum and minimum limit based on the number of bits used to represent them. Integers can be signed, or unsigned.

For instance, with 32-bit integers, the following ranges of numbers would be possible:

  • Signed: -2,147,483,648 to 2,147,483,647
  • Unsigned: 0 to 4,294,967,295

If an operation exceeds these limits, an overflow will occur.

Signed integers use two’s complement representation with the most significant bit (MSB) acting as the sign bit. In addition to the signed bit being set, the bits are inverted.

For instance, the number 5 would normally be represented via:

0000 0000 0000 0000 0000 0000 0000 0101

As such, -5 would become:

1111 1111 1111 1111 1111 1111 1111 1011

All bits are flipped, and the first bit is set to a 1. Since the first bit is used to specify the sign, this cannot be used for data storage.

The following code demonstrates how an overflow of a signed integer could occur.

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>

void print_binary(int n) {
    int num_bits = sizeof(int) * 8;  // Assumming 32 bits

    // Loop through each bit
    for (int i = num_bits - 1; i >= 0; i--) {
        // Print either 0 or 1 depending on the current bit
        printf("%d", (n >> i) & 1);

        // Print a space every 8 bits
        if (i % 8 == 0) {
            printf(" ");
        }
    }

    printf("\n");
}


int main() {

    int num1 = 10;
    printf("Maximum int value: %d\n", INT_MAX);
    print_binary(INT_MAX);
    printf("First number value: %d\n", num1);
    print_binary(num1);
    int input;

    printf("Enter a number to add: ");
    scanf("%d", &input);
    if (input <= 0) {
        printf("Invalid input! Please enter a positive number.\n");
        exit(0);
    }
    print_binary(input);

    num1 += input;
    printf("Result after addition: %d\n", num1);
    print_binary(num1);
    if (num1 < 10)
    {
	    puts("Authentication success!");
    }
    else
    {
	    puts("Authentication failed!");
    }

    return 0;
}

To reach the authentication success message, we need to enter a value below 10. However, under normal operation this isn’t possible since the code will always add 10 to the value we input.

./overflow 
Maximum int value: 2147483647
01111111 11111111 11111111 11111111 
First number value: 10
00000000 00000000 00000000 00001010 
Enter a number to add: 1
00000000 00000000 00000000 00000001 
Result after addition: 11
00000000 00000000 00000000 00001011 
Authentication failed!

If we enter the maximum integer value, the resulting value will become a negative integer and therefore pass our “security” check. This is because we overflow the maximum integer value, causing the Most Significant Bit (MSB) to become 1.

./overflow 
Maximum int value: 2147483647
01111111 11111111 11111111 11111111 
First number value: 10
00000000 00000000 00000000 00001010 
Enter a number to add: 2147483647
01111111 11111111 11111111 11111111 
Result after addition: -2147483639
10000000 00000000 00000000 00001001 
Authentication success!

Integer Underflows

Integer overflows are similar to overflows, but are caused by a value being stored in a integer variable that is smaller than the allowable range. The following code demonstrates this.

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>

void print_binary(int n) {
    int num_bits = sizeof(int) * 8;  // Assumming 32 bits

    // Loop through each bit
    for (int i = num_bits - 1; i >= 0; i--) {
        // Print either 0 or 1 depending on the current bit
        printf("%d", (n >> i) & 1);

        // Print a space every 8 bits
        if (i % 8 == 0) {
            printf(" ");
        }
    }

    printf("\n");
}


int main() {

    int num1 = -10;
    printf("Minimum int value: %d\n", INT_MIN);
    print_binary(INT_MIN);
    printf("First number value: %d\n", num1);
    print_binary(num1);
    int input;

    printf("Enter a number to add: ");
    scanf("%d", &input);
    if (input >= 0) {
        printf("Invalid input! Please enter a negative number.\n");
        exit(0);
    }
    print_binary(input);

    num1 += input;
    printf("Result after addition: %d\n", num1);
    print_binary(num1);
    if (num1 > 10)
    {
	    puts("Authentication success!");
    }
    else
    {
	    puts("Authentication failed!");
    }

    return 0;
}

Running the code, we can see we are unable to provide a positive value that’s greater than 10 to satisfy the security check.

./underflow 
Minimum int value: -2147483648
10000000 00000000 00000000 00000000 
First number value: -10
11111111 11111111 11111111 11110110 
Enter a number to add: -20
11111111 11111111 11111111 11101100 
Result after addition: -30
11111111 11111111 11111111 11100010 
Authentication failed!

Using a large negative integer, we can see the MSB is flipped to zero indicating the integer is a positive value. This bypasses our security check.

./underflow 
Minimum int value: -2147483648
10000000 00000000 00000000 00000000 
First number value: -10
11111111 11111111 11111111 11110110 
Enter a number to add: -2147483648
10000000 00000000 00000000 00000000 
Result after addition: 2147483638
01111111 11111111 11111111 11110110 
Authentication success!

Sign Mismatches

As stated previously, signed integers can represent both positive and negative values, while unsigned integers can only represent non-negative values (zero or positive).

If a signed integer is compared with an unsigned integer, the signed integer might be implicitly converted to an unsigned integer, causing incorrect comparisons if the signed integer is negative.

The following code demonstrates this concept.

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>

void print_binary(int n) {
    int num_bits = sizeof(int) * 8;  // Assumming 32 bits

    // Loop through each bit
    for (int i = num_bits - 1; i >= 0; i--) {
        // Print either 0 or 1 depending on the current bit
        printf("%d", (n >> i) & 1);

        // Print a space every 8 bits
        if (i % 8 == 0) {
            printf(" ");
        }
    }

    printf("\n");
}


int main() {

    unsigned int num1 = 10;
    printf("Initial value: %d\n", num1);
    print_binary(num1);
    int input;

    printf("Enter a number to compare: ");
    scanf("%d", &input);
    print_binary(input);

    if (input >= 10)
    {
	    puts("Input too large!");
	    exit(1);
    }

    if (input > num1)
    {
	    puts("Authentication success!");
    }
    else
    {
	    puts("Authentication failed!");
    }

    return 0;
}

Under normal circumstances authentication will fail since we’re unable to enter a value greater than 10.

./comparison 
Initial value: 10
00000000 00000000 00000000 00001010 
Enter a number to compare: 1
00000000 00000000 00000000 00000001 
Authentication failed!

Entering a negative value causes the initial value (00001010), to be compared with our value (11111111). As such, we meet the authentication success criteria.

./comparison 
Initial value: 10
00000000 00000000 00000000 00001010 
Enter a number to compare: -1
11111111 11111111 11111111 11111111 
Authentication success!

Truncation

Truncation can occur when a large variable value is assigned to a smaller variable, like an integer.

In the below example, we’re taking user input as a long long, which is essentially a 64-bit signed integer.

For instance,

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
void print_binary64(uint64_t value) {
    for (int i = 63; i >= 0; i--) {
        putchar((value & ((uint64_t)1 << i)) ? '1' : '0');
        if (i % 8 == 0 && i != 0) {
            putchar(' ');
        }
    }
    putchar('\n');
}

int main() {
    long long large_value;
    
    printf("Enter a large value (long long): ");
    scanf("%lld", &large_value);

    if (large_value <= 0) {
        printf("Invalid input! Please enter a positive number.\n");
        exit(0);
    }

    int small_value = (int) large_value;  // Truncation happens here

    printf("Original value: %lld\n", large_value);
    print_binary64(large_value);
    printf("Truncated value: %d\n", small_value);
    print_binary64(small_value);
    if (small_value < 0)
    {
	    puts("Authentication success!");
    }
    else
    {
	    puts("Authentication failed!");
    }


    return 0;
}

Authentication will fail under normal circumstances since we’re unable to enter a negative value.

./truncate
Enter a large value (long long): -1
Invalid input! Please enter a positive number.

When we enter a very large number, this can be stored in the long long, but gets truncated when it’s cast to an integer. The resulting value becomes negative since the MSB becomes a 1.

./truncate 
Enter a large value (long long): 111111111111
Original value: 111111111111
00000000 00000000 00000000 00011001 11011110 10111101 00000001 11000111
Truncated value: -558038585
11111111 11111111 11111111 11111111 11011110 10111101 00000001 11000111
Authentication success!