CertC-INT08¶
Verify that all integer values are in range
Required inputs: IR, StaticSemanticAnalysis
Integer operations must result in an integer value within the range of the integer type (that is, the resulting value is the same as the result produced by unlimited-range integers). Frequently, the range is more restrictive depending on the use of the integer value, for example, as an index. Integer values can be verified by code review or by static analysis.
Integer overflow is
undefined behavior, so a compiled program can do anything,
including go off to play the Game of Life. Furthermore, a compiler may perform
optimizations that assume an overflow will never occur, which can easily yield
unexpected results. Compilers can optimize away
if statements that check whether an overflow occurred. See
MSC15-C.
Do not depend on undefined behavior for an example.
Verifiably in-range operations are often preferable to treating out-of-range values as an error condition because the handling of these errors has been repeatedly shown to cause denial-of-service problems in actual applications. The quintessential example is the failure of the Ariane 5 launcher, which occurred because of an improperly handled conversion error that resulted in the processor being shut down [ Lions 1996].
A program that detects an integer overflow to be imminent may do one of two things: (1) signal some sort of error condition or (2) produce an integer result that is within the range of representable integers on that system. Some situations can be handled by an error condition, where an overflow causes a change in control flow (such as the system complaining about bad input and requesting alternative input from the user). Others are better handled by the latter option because it allows the computation to proceed and generate an integer result, thereby avoiding a denial-of-service attack. However, when continuing to produce an integer result in the face of overflow, the question of what integer result to return to the user must be considered.
The saturation and modwrap algorithms and the technique of restricted range
usage, defined in the following subsections, produce integer results that are
always within a defined range. This range is between the integer values
MIN and
MAX (inclusive), where
MIN and
MAX are two representable integers with
MIN < MAX.
Saturation Semantics
For saturation semantics, assume that the mathematical result of the
computation is
result. The value actually returned to the user is set out in the
following table:
| Range of Mathematical Result | Result Returned |
|---|---|
MAX < result |
MAX |
MIN <= result <= MAX |
result |
result < MIN |
MIN |
Modwrap Semantics
In modwrap semantics (also called modulo arithmetic), integer values
"wrap round." That is, adding 1 to
MAX produces
MIN. This is the defined behavior for unsigned integers in the C
Standard, subclause 6.2.5, paragraph 9. It is frequently the behavior of signed
integers, as well. However, it is more sensible in many applications to use
saturation semantics instead of modwrap semantics. For example, in the
computation of a size (using unsigned integers), it is often better for the
size to stay at the maximum value in the event of overflow rather than to
suddenly become a very small value.
Restricted Range Usage
Another technique for avoiding integer overflow is to use only half the
range of signed integers. For example, when using an
int, use only the range [
INT_MIN/2,
INT_MAX/2]. This practice has been a trick of the trade in Fortran
for some time, and now that optimizing C compilers are more sophisticated, it
can be valuable in C.
Consider subtraction. If the user types the expression
a - b, where both
a and
b are in the range
[INT_MIN/2, INT_MAX/2], the result will be in the range
(INT_MIN, INT_MAX] for a typical two's complement machine.
Now, if the user types
a < b, an implicit subtraction often occurs. On a machine
without condition codes, the compiler may simply issue a subtract instruction
and check whether the result is negative. This behavior is allowed because the
compiler is allowed to assume there is no overflow. If all explicitly
user-generated values are kept in the range
[INT_MIN/2, INT_MAX/2], then comparisons will always work even if
the compiler performs this optimization on such hardware.
Noncompliant Code Example
In this noncompliant example,
i + 1 will overflow on a 16-bit machine. The C Standard allows
signed integers to overflow and produce incorrect results. Compilers can take
advantage of this to produce faster code by assuming an overflow will not
occur. As a result, the
if statement that is intended to catch an overflow might be
optimized away.
int i = /* Expression that evaluates to the value 32767 */;
/* ... */
if (i + 1 <= i) {
/* Handle overflow */
}
/* Expression involving i + 1 */
Compliant Solution
Using a
long instead of an
int is guaranteed to accommodate the computed value:
long i = /* Expression that evaluates to the value 32767 */; /* ... */ /* No test is necessary; i is known not to overflow */ /* Expression involving i + 1 */
Risk Assessment
Out-of-range integer values can result in reading from or writing to arbitrary memory locations and the execution of arbitrary code.
| Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| INT08-C | Medium | Probable | High | P4 | L3 |
Related Guidelines
| SEI CERT C++ Coding Standard | VOID INT08-CPP. Verify that all integer values are in range |
| ISO/IEC TR 24772:2013 | Numeric Conversion Errors [FLC] |
Bibliography
| [ Lions 1996] |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
cast_overflow |
Cast on result of arithmetic computation may cause overflow |
None |
False |
cast_truncate |
Cast may truncate value |
None |
False |
cast_underflow |
Cast on result of arithmetic computation may cause underflow |
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 |
overflow |
Arithmetic computation may cause overflow |
None |
False |
shift_amount_negative |
Possible shift by a negative bit count (undefined behavior) |
None |
False |
shift_amount_too_large |
Possible shift by the integer width or more (undefined behavior) |
None |
False |
shift_right_negative |
Possible right shift with negative left-hand-side |
None |
False |
static_cast_overflow |
Cast on result of arithmetic computation may cause overflow |
None |
False |
static_cast_underflow |
Cast on result of arithmetic computation may cause underflow |
None |
False |
static_cast_underflow_minus_1 |
Casting -1 to an unsigned type causes underflow |
None |
False |
static_overflow |
Arithmetic computation may cause overflow |
None |
False |
static_underflow |
Arithmetic computation may cause underflow |
None |
False |
underflow |
Arithmetic computation may cause underflow |
None |
False |
unsigned_cast_overflow |
Cast on result of arithmetic computation may cause wrap-around |
None |
False |
unsigned_cast_underflow |
Cast on result of arithmetic computation may cause wrap-around (underflow) |
None |
False |
unsigned_overflow |
Arithmetic computation may cause wrap-around |
None |
False |
unsigned_underflow |
Arithmetic computation may cause wrap-around (underflow) |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
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
abstract_interpretation_overflow_unrolling_level¶
abstract_interpretation_overflow_unrolling_level : int = 0
check_signed¶
check_signed : bool = True
check_unsigned¶
check_unsigned : bool = True
relevant_expressions¶
relevant_expressions
Which (const / constant) expressions should be considered.Type: RelevantExpressions
Default:
'const_and_compile_time_constant'
Note: this is only relevant for the purely static parts of the analysis. The StaticSemanticAnalysis-based checks for runtime errors will be performed independently.
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:
RelevantExpressions¶
An enumeration.none
No (additional) checks for overflows in const-expressions or compile time constant expressions.const_expressions_only
Whether the analysis should statically check const-expressions (i.e., const variables and literals) that might have been reduced to a literal during compilation.const_and_compile_time_constant
Whether the analysis should statically check const-expressions (i.e., const variables and literals) as well as compile-time constant expressions (i.e., preprocessor defines, constexprs or literals) that might have been reduced to a literal during compilation.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.