C++ has the vulnerability as documented in ISO/IEC 24772-1 clause 6.15, since overflow situations are undefined behaviour [EWF] for signed integer arithemtic and wrap-around for unsigned integer arithmetic, which can lead to surprising results. C++ specifies that
For example, integral promotion happens when multiplying two unsigned short
operands which can result in undefined behavior:
auto f(){
std::uint16_t x{50'000},y{50'000};
return x * y; // undefined behaviour due to overflow, returns int
}
In the above, for a 16-bit short
and a 32-bit int
, i.e., std::numeric_limits<int>::max()==0x7fff'ffff
, x and y are promoted to int
and the multiplication then overflows which is undefined behaviour.
Even when operands have the same unsigned type, wrap-around arithmetic can be confusing, for example, 4U - 5U
yields a large positive value.
Calling a function taking a parameter of integral type with an argument of different integral type works due to implicit conversions. If a different overload with a better match becomes visible the called function can change when re-compiled (see 6.21 Namespace Issues[BJL])
Using brace-initialization prevents implicit narrowing conversions in contrast to other forms of initialization. For example:
std::uint16_t x{500'000}; // won't compile due to narrowing
std::uint16_t y = 500'000; // compiles, but truncates value
The mitigations for wrap-around errors in C++ are different than for C. The type system of C++ allows user-defined class and enum types with corresponding overloaded operators. Such user-defined types can individually control which implicit conversions or mixed type arithmetic they support, if any. For example, one can force arithmetic to be done with unsigned types:
enum class uint16: std::uint16_t{};
operator*(uint16 a, uint16 b){
uint16 return static_cast<uint16>(static_cast<unsigned>(a) * static_cast<unsigned>(b));
// guarantee wrap-around }
High-integrity software using the built-in integral types should
<cstdint>
),To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Use the avoidance mechanisms of ISO/IEC 24772-1 clause 6.15.5.
Use appropriate user-defined types with well-defined range in place of built-in integral types, that mitigate against implicit conversions and undefined behaviour.
Use types with a well-defined range and have compile-time guarantees that the range of types used is sufficient, i.e., with static_assert
.
Avoid implicit integral promotions and integral conversions, especially on function arguments.
Avoid mixing integral types with different size or different signedness in the same expression.
Ensure that the result of any mathematical operation fits within the constraints of the types involved within the expression.
Use unsigned types that do not promote to int
to avoid undefined behavior due to signed integer overflow.
Consider the use of numeric_limits<T>::is_modulo
to determine whether or not an integer type T
wraps for the target system.
Document where wrap-around is expected for a type.
Use static and dynamic analysis tools to detect problematic expressions.