Skip to content

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

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, memcpy length, 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/MAX constants)
  • 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 int against uint to long, 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.toIntExact and range checks in Java, checked casts 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). /W4 alone 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 + 1 narrowed to int)
  • 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)value instead of value silences 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 negative signed_value becomes 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 negative len, 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.

Additional Resources