CWE-192: Integer Coercion Error
Overview
Integer coercion errors occur when an implicit conversion between integer types - a narrowing cast (long to int), a sign change (unsigned to signed or back), or a parameter conversion at a function call - silently changes the value instead of raising an error. The converted value then flows into a buffer size, an array index, a comparison, or a security check as if it were still correct.
Relationship to Other CWEs
- CWE-192 (this page) - an implicit integer conversion that silently changed a value which was in range for its original type
- CWE-681 (Incorrect Conversion between Numeric Types) - the parent this Variant sits under. No page here
- CWE-195 (Signed to Unsigned Conversion Error), CWE-196 (Unsigned to Signed Conversion Error) and CWE-197 (Numeric Truncation Error) - the three more specific sibling variants documented here. Use one of those when the finding is specifically a sign flip or a size truncation, and this page for the general case
- CWE-191 (Integer Underflow) - different: this page is a conversion changing a value, not an arithmetic result falling out of range
Coercion is not confined to unmanaged code. C and C++ perform the conversion
implicitly. Java and C# demand a visible cast, but the cast still truncates
without complaint: (int) longValue in Java keeps the low 32 bits, and C#
does the same outside a checked block. The difference between the languages
is where the wrong value surfaces, not whether it is produced.
Risk
Medium-High: A narrowing conversion that truncates a large size to a small one causes undersized allocations followed by buffer overflows on the subsequent write. A sign change turns a validated positive value negative (or vice versa), which can flip the outcome of a comparison used for a security check, an array index, or a loop bound.
Remediation Steps
Core Principle: Never rely on an implicit conversion between integer types - convert explicitly, and validate that the value fits the target type's range before converting.
Trace the Data Path
- Source: Any value crossing a type boundary - a wider type assigned to a narrower one, a size/length value assigned to a smaller counter or parameter, an unsigned value compared against or assigned to a signed one
- Sink: Wherever the converted value is used - buffer allocation,
memcpylength, array index, a loop bound, or a comparison gating a security decision - Missing Controls: No explicit range check before the conversion; a function signature whose parameter type is narrower than the type of some or all of its callers' arguments
Convert Explicitly, and Validate the Range First (Primary Defense)
- Never let a compiler perform a narrowing or sign-changing conversion implicitly - cast explicitly, and check first that the value fits in the target type's range (its
MIN/MAXconstants) - On a failed range check, reject the value or raise an error - do not silently clamp or truncate, since that produces a different but still-wrong value
- Wrap the check-then-cast pattern in a small, reusable helper so every conversion site gets the same validation rather than repeating ad hoc logic
Keep Comparisons and Arithmetic in One Type (Defense in Depth)
- Compare and combine values using a single, consistent type rather than mixing signed and unsigned. In C and C++, when the unsigned operand is at least as wide as the signed one, the signed operand is converted to unsigned and a negative value becomes a very large positive one. Java has no unsigned integer type and C# widens
intagainstuinttolong, so neither reproduces that conversion; what they share with C is the silent narrowing cast - Prefer the language's overflow/range-checked conversion primitives where available (
Math.toIntExactand range checks in Java,checkedcasts in C#,numeric_limits-based validation in C++) over a bare cast - Enable and treat as errors the compiler warnings that catch this class of bug:
-Wconversion -Wsign-conversion -Wsign-compare(GCC/Clang) or/W4 /w44365(MSVC)./W4alone covers C4018 for a signed/unsigned comparison and C4244/C4267 for a narrowing conversion, but not C4365, the signed/unsigned conversion warning, which is off by default and has to be enabled by number
Test with Boundary and Truncating Values
- A value exactly at, one above, and one below the target type's
MIN/MAX - A negative value passed where an unsigned parameter or comparison is expected
- A value that fits the source type but not the destination type (e.g.,
INT_MAX + 1narrowed toint) - Re-scan with the security scanner to confirm the finding is resolved
Common Vulnerable Patterns
// VULNERABLE - pseudo-code
function allocate_buffer(size: int64):
// implicit narrowing conversion, no range check
buffer_size: int32 = size // e.g. 0x100000001 truncates to 1
buffer = allocate(buffer_size) // allocates far less than intended
Why this is vulnerable: the conversion is implicit, so there is no cast to notice and nothing in the line marks a boundary being crossed. size and buffer_size are both integers and both named as sizes; the only thing that differs is a width declared elsewhere, possibly in another file, and the truncation happens silently at the assignment.
What comes out is a valid number, which is why nothing downstream can recover. allocate receives 1 and allocates 1 correctly - there is no error to check and no state that records what was asked for. The caller keeps the original size and uses it to bound the writes, so the two values disagree from this line onward and the mismatch is what produces the overflow. The check has to sit on the wide value before the assignment; once the narrowing has happened, the information needed to detect it is gone.
Secure Patterns
// SECURE - pseudo-code
function allocate_buffer(size: int64):
if size < INT32_MIN or size > INT32_MAX:
reject("size out of range")
buffer_size: int32 = explicit_cast(size)
buffer = allocate(buffer_size)
Why this works: Validating that the value fits the target type's range before converting means no bits are silently discarded and no sign flip goes unnoticed. Making the cast explicit also documents, at the call site, that a conversion is intentionally happening rather than occurring as an invisible side effect of assignment.
Common Pitfalls
- Adding the explicit cast without the range check: Writing
(int)valueinstead ofvaluesilences the compiler warning but performs exactly the same silent truncation - the cast alone validates nothing, it just makes the existing bug quieter. - Comparing a signed and unsigned value directly:
if (signed_value < unsigned_size)converts the signed operand to unsigned, so a negativesigned_valuebecomes a huge positive number and the comparison answers false - the guarded branch is skipped, which is the safe direction and is why this shape is easy to miss in review. The hazard is the opposite arrangement: an upper-bound-only check evaluated in signed arithmetic (if (len > max) reject) accepts a negativelen, which then converts to an enormous value at an unsigned sink such as an allocation or a copy length. Check the sign explicitly before either the comparison or the sink. - Clamping instead of rejecting an out-of-range value: Silently clamping a too-large value to the target type's maximum avoids a crash but substitutes a different, still-incorrect value into the calculation - if the value was meant to represent a real size or count, clamping produces a working but wrong result rather than surfacing the error.
- Validating only the happy-path call site: A helper function's parameter type is narrower than its callers' argument types at every call site, not just the one that was flagged - fixing one caller without checking the rest leaves the same truncation reachable elsewhere.