CWE-197

Numeric Truncation Error. [Improper-Control-Of-A-Resource-Through-Its-Lifetime]

Required inputs: IR, StaticSemanticAnalysis

Truncation errors occur when a primitive is cast to a primitive of a smaller size and data is lost in the conversion. When a primitive is cast to a smaller primitive, the high order bits of the large value are lost in the conversion, potentially resulting in an unexpected value that is not equal to the original value. This value may be required as an index into a buffer, a loop iterator, or simply necessary state data. In any case, the value cannot be trusted and the system will be in an undefined state. While this method may be employed viably to isolate the low bits of a value, this usage is rare, and truncation usually implies that an implementation error has occurred.
Demonstrative Examples
Example 1

This example, while not exploitable, shows the possible mangling of values associated with truncation errors:

Example Language:C
    int intPrimitive;
    short shortPrimitive;
    intPrimitive = (int)(~((int)0) ^ (1 << (sizeof(int)*8-1)));
    shortPrimitive = intPrimitive;
    printf("Int MAXINT: %d\nShort MAXINT: %d\n", intPrimitive, shortPrimitive);

The above code, when compiled and run on certain systems, returns the following output:

(result)

    Int MAXINT: 2147483647
    Short MAXINT: -1

This problem may be exploitable when the truncated value is used as an array index, which can happen implicitly when 64-bit values are used as indexes, as they are truncated to 32 bits.

Example 2

In the following Java example, the method updateSalesForProduct is part of a business application class that updates the sales information for a particular product. The method receives as arguments the product ID and the integer amount sold. The product ID is used to retrieve the total product count from an inventory object which returns the count as an integer. Before calling the method of the sales object to update the sales count the integer values are converted to The primitive type short since the method requires short type for the method arguments.

Example Language:Java (Unsupported language for documentation only)
    ...
    // update sales database for number of product sold with product ID
    public void updateSalesForProduct(String productID, int amountSold) {
        // get the total number of products in inventory database
        int productCount = inventory.getProductCount(productID);
        // convert integer values to short, the method for the

        // sales object requires the parameters to be of type short
        short count = (short) productCount;
        short sold = (short) amountSold;
        // update sales database for product
        sales.updateSalesCount(productID, count, sold);
    }
    ...

However, a numeric truncation error can occur if the integer values are higher than the maximum value allowed for the primitive type short. This can cause unexpected results or loss or corruption of data. In this case the sales database may be corrupted with incorrect data. Explicit casting from a from a larger size primitive type to a smaller size primitive type should be prevented. The following example an if statement is added to validate that the integer values less than the maximum value for the primitive type short before the explicit cast and the call to the sales method.

Example Language:Java (Unsupported language for documentation only)
    ...
    // update sales database for number of product sold with product ID
    public void updateSalesForProduct(String productID, int amountSold) {
        // get the total number of products in inventory database
        int productCount = inventory.getProductCount(productID);
        // make sure that integer numbers are not greater than

        // maximum value for type short before converting
        if ((productCount < Short.MAX_VALUE) && (amountSold < Short.MAX_VALUE)) {
            // convert integer values to short, the method for the

            // sales object requires the parameters to be of type short
            short count = (short) productCount;
            short sold = (short) amountSold;
            // update sales database for product
            sales.updateSalesCount(productID, count, sold);

        else {
        // throw exception or perform other processing

            ...
        }
    }
    ...
Excerpts from CWE [https://cwe.mitre.org], Copyright (C) 2006-2026, the MITRE Corporation. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

Possible Messages

Key

Text

Severity

Disabled

cast_truncate

Possible numeric truncation error.

None

False

cast_underflow

Possible numeric truncation error.

None

False

certain_shift_amount_negative

Shift by a negative bit count (undefined behavior)

None

False

certain_shift_amount_too_large

Shift by the integer width or more (undefined behavior)

None

False

certain_shift_right_negative

Right shift with negative left-hand-side

None

False

underflow

Possible numeric truncation error.

None

False

unsigned_cast_underflow

Possible numeric truncation error.

None

False

unsigned_underflow

Possible numeric truncation error.

None

False

Options

abstract_interpretation_maximal_tracked_array_index

abstract_interpretation_maximal_tracked_array_index : int = 10

The number of explicit indices in array expressions per routine tracked by the "symbolic expression analysis". For example, consider the following program.

extern signed char a[6];
int main()
{
    if (a[2] < 0)
    {
        a[2]++;
    }
    if (a[3] < 0)
    {
        a[3]++;
    }
    if (a[4] < 0)
    {
        a[4]++;
    }
    return 0;
}

If the value of this option is set to 2, the first two array index expressions encountered in the routine are tracked. Hence, the analysis can use the facts a[2] < 0 and a[3] < 0 to infer that a[2]++ and a[3]++ do not overflow, but it will not track the third array access in this routine.

A higher value of the option can cause more consumption of memory and time for the analysis.

 

abstract_interpretation_overflow

abstract_interpretation_overflow : bool = False

Use abstract-interpretation-based "symbolic expression analysis" as additional postprocessing step.
 

abstract_interpretation_overflow_unrolling_level

abstract_interpretation_overflow_unrolling_level : int = 0

How many levels of conditions are traversed to compute additional constraints for the "symbolic expression analysis".
 

check_signed

check_signed : bool = True

Whether issues for signed integer operations should be reported. For casts including implicit conversions, the target type of the cast is used.
 

check_unsigned

check_unsigned : bool = True

Whether wrap-around for unsigned integer operations should be reported. For casts including implicit conversions, the target type of the cast is used.
 

suppress_well_defined_findings

suppress_well_defined_findings : SuppressionMode = 'NONE'

Some overflows have well-defined semantics in all C/C++ standard versions. The typical example is UINT_MAX+1 which is well-defined as 0 via wraparound. This differs from INT_MAX+1 which is either undefined or implementation-defined depending on the considered standard version. Most CPUs will compute INT_MIN but this wraparound is not guaranteed by any C/C++ standard.

Both cases are overflows and are reported by this rule. However, one might want to suppress messages for the well-defined cases. To suppress these activate this option.

Different C and C++ standard versions differ in what is well-defined, implementation-defined, or undefined. Luckily, if we only consider well-defined and do not discern between implementation-defined and undefined, we end up with only two groups: pre-C++20 and since-C++20.

 

Option Types

These types are used by options listed above:

SuppressionMode

An enumeration.
 

NONE

Suppress nothing.

PRE_CPP2020

Suppress findings that are well-defined before C++20. These are:

  • Over- and underflows of unsigned integers during addition, subtraction, and multiplication
  • Conversions from unsigned to unsigned integers
  • Wrap-around caused by left-shifting of unsigned integer

CPP2020

Suppress findings that are well-defined since C++20. These are:

  • Over- and underflows of unsigned integers during addition, subtraction, and multiplication
  • Conversions between signed and unsigned integers
  • Wrap-around caused by left-shifting
  • Shifting negative integers

Surprising mechanics of C++20 signed narrow integers

Since C++20, casts between signed and unsigned are defined as two-complement wrap-around. Overflows of signed integers are still undefined behavior and are reported by this rule. But, due to integer promotion rules, certain expressions are computed using wider integer types, which can lead to the false impression that this is no longer the case, because no overflow findings are reported there.

Suppose, that the code is compiled on a platform where short is smaller than or equal to half the size of an int. Very commonly the sizes are 2 and 4. This assumption is thus true for many platforms.

In this case, narrow signed integer types such as short or signed char are first implicitly promoted to int before the arithmetic operation is executed. Because of this promotion, the actual operation does not overflow and is thus well-defined. After the operation, an implicit cast is performed to the narrower type. This cast is well-defined in C++20 as wrapping around.

Consider the following snippet:

    static_assert(sizeof(short) == 2);
    static_assert(sizeof(int) == 4);
    short a = 0x1000;
    short b = 0x1001;
    short c = a*b;
C++20 defines c as 0x1000. The reason is that a*b is implicitly promoted to static_cast<int> (a)*static_cast<int>(b). After the promotion, the multiplication does not overflow and yields a well-defined 0x1001000. This number is then implicitly cast to 0x1000 which is also a well-defined operation.

An analogous effect can be observed for signed short addition and multiplication. Another effect is that it is well-defined to shift by up to as many bits as int has even if the shifted integer has fewer bits.

DERIVE_FROM_IR

Derive the language version from the IR compilation flags and suppress findings accordingly.