Integer Errors

Summary

Integer values that are too large or too small may fall outside the allowable bounds for their data type, leading to unpredictable problems that can both reduce the robustness of your code and lead to potential security problems.

Description

Declaring a variable as type int allocates a fixed amount of space in memory. Most languages include several integer types, including short, int, long, etc., to allow for less or more storage. The amount of space allocated limits the range of values that can be stored. For example, a 32-bit signed int variable can hold values from \(- 2^{-31}\) through \(2^{31} - 1\).

Operations that result in values outside of this range can lead to a variety of problems, which apply to all data types that store integer variables, including int, short, and long.

  • Integer Overflow/Underflow: Mathematical operations can increase integer values above the maximum or decrease them below the minimum allowed values. For instance, if i = Integer.MAX_VALUE, the increment operator i++ will cause an overflow and the resulting value will be the smallest possible integer value Integer.MIN_VALUE. Addition, subtraction, multiplication, and even division can cause over􀀂ow problems.

  • Truncation: If an integer is converted from a larger type to a smaller type (say, from a long to a short), the value will be truncated if it is outside the range of the smaller type: the high-order bits that do not fit into the smaller type will simply be thrown out. For example, if you have a long variable that has a value that is greater than the largest value that can be stored in a short (long l=32800), assigning the long to a short (short s = (short)l;) will cause a truncation error (s=-32736).

Risk

How Can It Happen? Integer variables are often used to indicate the number of items that must be stored in an array or other fixed-size buffer. If a variable with an overflow is used for this purpose, the resulting buffer might be too small for the data.

Example

Many Unix operating systems store time values in 32-bit signed (positive or negative) integers, counting the number of seconds since midnight on January 1, 1970. On Tuesday, January 19, 2038, this value will overflow, becoming a negative number. Although the impact of this problem in 2038 is not yet known, there are concerns that software that projects out to future dates - including tools for mortgage payment and retirement fund distribution - might face problems long before then.

Example Code (Bad Code)

In this example the method determineFirstQuarterRevenue is used to determine the first quarter revenue for an accounting/business application. The method retrieves the monthly sales totals for the first three months of the year, calculates the first quarter sales totals from the monthly sales totals, calculates the first quarter revenue based on the first quarter sales, and finally saves the first quarter revenue results to the database.

...
final int JAN = 1;
final int FEB = 2;
final int MAR = 3;

...

short getMonthlySales(int month) {...}

float calculateRevenueForQuarter(short quarterSold) {...}

int determineFirstQuarterRevenue() {

  // Variable for sales revenue for the quarter
  float quarterRevenue = 0.0f;

  short JanSold = getMonthlySales(JAN); /* Get sales in January */
  short FebSold = getMonthlySales(FEB); /* Get sales in February */
  short MarSold = getMonthlySales(MAR); /* Get sales in March */

  // Calculate quarterly total
  short quarterSold = JanSold + FebSold + MarSold;

  // Calculate the total revenue for the quarter
  quarterRevenue = calculateRevenueForQuarter(quarterSold);

  saveFirstQuarterRevenue(quarterRevenue);

  return 0;
}
...

However, in this example the primitive type short int is used for both the monthly and the quarterly sales variables. In Java the short int primitive type has a maximum value of 32768. This creates a potential integer overflow if the value for the three monthly sales adds up to more than the maximum value for the short int primitive type. An integer overflow can lead to data corruption, unexpected behavior, infinite loops and system crashes. To correct the situation the appropriate primitive type should be used, as in the example below, and/or provide some validation mechanism to ensure that the maximum value for the primitive type is not exceeded. Note that an integer overflow could also occur if the quarterSold variable has a primitive type long but the method calculateRevenueForQuarter has a parameter of type short.

Addressing Integer Errors

How would we address potential integer errors in our code? Below are a few tips:

  1. Know your limits: Familiarize yourself with the ranges available for each data type. Since the size of data types is compiler and machine dependent, it is a good idea to write a short program to show you the limits of each variable type. For instance, you can run the following Java code to see the limits of a few integer data types in your system:

    public class IntegerCheck {
       public static void main(String[] args) {  
         int i;
         byte b;
         short sh;
         long lon;
        
         System.out.println("Valid integer is between " + Integer.MIN_VALUE + " and " + Integer.MAX_VALUE);
         System.out.println("Valid byte is between " + Byte.MIN_VALUE + " and " + Byte.MAX_VALUE);
         System.out.println("Valid short is between " + Short.MIN_VALUE + " and " + Short.MAX_VALUE );
         System.out.println("Valid long is between " + Long.MIN_VALUE + " and " + Long.MAX_VALUE);
       }
     }
    
  2. Validate your input: Check input for range and reasonableness before conducting operations. 1. Check for possible overflows: Always check results of arithmetic operations or parsing of strings to integers, to be sure that an overflow has not occurred. The result of multiplying two positive integers should be at least as big as both of those integers. If you find a result that overflows, you can take appropriate action before the result is used. Imagine you were adding two positive integers. Instead of writing:
    int sum = someInteger + someOtherInteger;
    

    You might try this instead:

    if (((someInteger > 0) && (someOtherInteger > 0)
          && (someInteger > (Integer.MAX_VALUE - someOtherInteger)))
       ||
        ((someInteger < 0) && (someOtherInteger < 0) 
          && (someInteger < (Integer.MIN_VALUE - someOtherInteger)))) {
      // throw an exception to handle the problem
    } else {
      // no exception, so no overflow.
    }
    

    This won’t completely solve the problem - you’ll need to recover from the exception. As these tests can become fairly complex, you should be careful when relying upon manual checks for preventing integer overflow.

  3. Use Compiler Checks. Many compilers include options that can be used to check for possible integer overflows. Read the documentation for your compiler and use these functions whenever possible.
  4. Use libraries that will help you avoid errors: C++ programmers might use the SafeInt class, which tests for errors before conducting operations. Java programmers can use the BigInteger class to conduct arbitrary-precision integer operations that might go beyond the bounds of long integers. However, be aware of the performance implications of using these libraries.

Fixing Weaknesses

In this lab, you will be given a code snippet that contains an Integer Error vulnerability. Your task is to identify the vulnerability and fix it.

Examine the code snippet below.

import java.util.Scanner;
public class PopCheck {
  public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    System.out.print("What is the current population? ");
    short pop = scan.nextShort();
    System.out.print("What is the rate of growth? (e.g., for 10% enter 10)");
    short growth = scan.nextShort(); //note that growth is an integer

    float growthRate = growth / 100f;    //convert the growth rate to a float
    System.out.println("Year\tGrowth\tNew Population");
    for (int i = 1; i <= 10; i++)  {
      // calculate increase and new population
      // Force them both to be shorts
      short increase = (short) (pop * growthRate);
      short newpop = (short) (pop + increase);
      System.out.println(i + "\t" + increase + "\t" + newpop);
      pop = newpop;
    }
    System.err.println("Final population is " + pop);
  }
} 
  1. Locate the lines of code where the Integer Error vulnerability occurs. Hint: What happens if you start with a population of 20,000 and a 10% growth rate?
  2. Fix the security weakness using the skills you just learned, i.e., revise the program to either remove the Integer Error or to raise an exception when it occurs.
  3. Complete the survey in the next page. One of the question in the survey asks for your solution to the lab. Copy your solution code snippet (snippet only) and paste it into the survey.

Acknowledgement

This page is derived from the Security Injection@Towson project.