Skip to content

CWE-192: Integer Coercion Error

Overview

Integer coercion errors occur when implicit type conversions between different integer types (int to short, long to int, signed to unsigned) cause unexpected value changes, truncation, or sign extension, leading to incorrect program behavior, buffer overflows, or security bypasses.

Risk

Medium-High: Implicit integer conversions cause value truncation (losing high-order bits), sign changes (negative becomes positive), incorrect comparisons, buffer overflows when used for sizes/indices, and logic errors. Especially dangerous when converting from larger to smaller types or between signed/unsigned.

Remediation Steps

Core principle: Avoid unsafe integer coercions; normalize types and validate before conversions and comparisons.

Locate Integer Coercion Errors in Your Code

When reviewing security scan results:

  • Find implicit conversions: Identify where values are converted between integer types without explicit casts
  • Check type mismatches: Look for assignments or function calls with different integer types (long→int, unsigned→signed, int64_t→int32_t)
  • Identify truncation risks: Find conversions from larger to smaller types (long to int, int to short, size_t to int)
  • Trace data flow: Determine where converted values are used (buffer sizes, array indices, loop bounds, security checks)
  • Review function signatures: Check parameter types vs. argument types at call sites

Common problematic conversions:

// Large to small
long big_value = 0x100000001;
int small = big_value;  // Truncates to 1

// Unsigned to signed
size_t len = strlen(input);  // unsigned
int size = len;  // Could overflow if len > INT_MAX

// Signed to unsigned
int offset = -1;
size_t index = offset;  // Becomes SIZE_MAX

// Function parameter coercion
void process(short value);  // Expects short
int user_input = 100000;
process(user_input);  // Silently truncates

Use Explicit Type Conversions with Validation (Primary Defense)

#include <limits.h>
#include <stdbool.h>
#include <stdio.h>

// VULNERABLE - implicit conversion loses data
void allocate_buffer_bad(long size) {
    // Attack: size = 0x100000001 (4GB + 1)
    int buffer_size = size;  // Implicitly truncates to 1!
    char *buffer = malloc(buffer_size);  // Allocates 1 byte
}

// SECURE - validate before conversion
bool safe_long_to_int(long value, int *result) {
    // Check value fits in int range
    if (value < INT_MIN || value > INT_MAX) {
        return false;  // Out of range
    }
    *result = (int)value;  // Explicit cast
    return true;
}

void allocate_buffer_safe(long size) {
    int buffer_size;

    // Validate conversion is safe
    if (!safe_long_to_int(size, &buffer_size)) {
        fprintf(stderr, "Size out of int range: %ld\n", size);
        return;
    }

    // Additional validation
    if (buffer_size <= 0 || buffer_size > 10000000) {
        fprintf(stderr, "Invalid buffer size: %d\n", buffer_size);
        return;
    }

    char *buffer = malloc(buffer_size);
}

// Safe conversion helpers for common types
bool safe_size_t_to_int(size_t value, int *result) {
    if (value > INT_MAX) {
        return false;
    }
    *result = (int)value;
    return true;
}

bool safe_uint64_to_uint32(uint64_t value, uint32_t *result) {
    if (value > UINT32_MAX) {
        return false;
    }
    *result = (uint32_t)value;
    return true;
}

Why this works: Explicit conversions with validation detect value truncation or sign changes before they cause security issues. Always validate the value fits in the target type's range.

Key principles:

  • Never rely on implicit conversions - always use explicit casts
  • Validate before narrowing - check value fits in target type
  • Use constants: INT_MIN, INT_MAX, SIZE_MAX, etc.
  • Return error on failure - don't silently truncate

Avoid Mixing Signed and Unsigned Types

#include <string.h>
#include <stddef.h>

// VULNERABLE - mixing signed and unsigned
int copy_data_bad(char *dest, int dest_size, char *src) {
    size_t src_len = strlen(src);  // Returns size_t (unsigned)

    // DANGEROUS: comparing unsigned with signed
    // If dest_size is negative, it gets promoted to huge unsigned!
    if (src_len < dest_size) {
        memcpy(dest, src, src_len);
    }
}

// SECURE - use consistent types
int copy_data_safe(char *dest, size_t dest_size, char *src) {
    size_t src_len = strlen(src);

    // Both size_t - safe comparison
    if (src_len >= dest_size) {
        return -1;  // Won't fit
    }

    memcpy(dest, src, src_len);
    dest[src_len] = '\0';
    return 0;
}

// VULNERABLE - signed/unsigned in loop
void process_backwards_bad(unsigned int count) {
    int i;  // Signed!
    for (i = count - 1; i >= 0; i--) {
        // If count is large, count-1 might not fit in int
        process(data[i]);
    }
}

// SECURE - consistent types
void process_backwards_safe(size_t count) {
    if (count == 0) return;

    // Use same type for loop variable
    for (size_t i = count; i-- > 0; ) {
        process(data[i]);
    }
}

// Comparison pitfall example
void demonstrate_comparison_issue() {
    unsigned int u = 10;
    int s = -5;

    // WRONG: s is promoted to unsigned!
    // -5 becomes a huge positive number
    if (s < u) {
        printf("This won't print!\n");
    }

    // CORRECT: cast to same type
    if ((int)u > s && u < INT_MAX) {
        printf("Safe comparison\n");
    }
}

Enable Compiler Warnings and Use Static Analysis

# GCC/Clang - enable conversion warnings
gcc -Wconversion -Wsign-conversion -Wsign-compare -Werror \

    -Wall -Wextra -o program program.c

# Clang - more strict
clang -Wconversion -Wshorten-64-to-32 -Wsign-conversion \

      -Werror -o program program.c

# MSVC - enable all warnings
cl /W4 /WX program.c

Common warnings to watch for:

// warning: implicit conversion loses integer precision
long l = 100000;
short s = l;  // Warning!

// warning: comparison of integers of different signs
int a = -1;
unsigned int b = 10;
if (a < b) {}  // Warning!

// warning: conversion from 'size_t' to 'int'
size_t len = strlen(str);
int n = len;  // Warning!

Static analysis tools:

  • Coverity (finds implicit conversions)
  • Clang Static Analyzer
  • PVS-Studio
  • Veracode Static Analysis
  • Cppcheck with --enable=portability

Use Type-Safe APIs and Language Features

#include <limits>
#include <stdexcept>
#include <type_traits>
#include <cstdint>

// C++ template for safe narrowing conversion
template<typename Target, typename Source>
Target safe_cast(Source value) {
    // Check if value fits in target type
    if (value < std::numeric_limits<Target>::min() ||
        value > std::numeric_limits<Target>::max()) {
        throw std::overflow_error("Value out of target type range");
    }
    return static_cast<Target>(value);
}

void allocate_buffer_cpp(int64_t size) {
    // Explicit safe conversion
    int32_t buffer_size = safe_cast<int32_t>(size);

    if (buffer_size <= 0 || buffer_size > 10000000) {
        throw std::invalid_argument("Invalid buffer size");
    }

    auto buffer = std::make_unique<char[]>(buffer_size);
}

// Use sized types for clarity
void use_sized_types() {
    int32_t small;   // Explicitly 32-bit
    int64_t large;   // Explicitly 64-bit

    large = 0x100000000LL;

    // Safe conversion with validation
    small = safe_cast<int32_t>(large);
}

// Java example
public class SafeConversion {
    public static int safeLongToInt(long value) {
        if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
            throw new IllegalArgumentException(
                "Value " + value + " out of int range");
        }
        return (int) value;
    }

    public static byte[] allocateBuffer(long size) {
        int bufferSize = safeLongToInt(size);

        if (bufferSize <= 0 || bufferSize > 10_000_000) {
            throw new IllegalArgumentException("Invalid buffer size");
        }

        return new byte[bufferSize];
    }
}

// C# example with checked blocks
public class SafeConversion {
    public static int SafeLongToInt(long value) {
        checked {  // Throws OverflowException on overflow
            return (int)value;
        }
    }

    public static byte[] AllocateBuffer(long size) {
        int bufferSize;
        try {
            bufferSize = SafeLongToInt(size);
        } catch (OverflowException) {
            throw new ArgumentException("Size out of range");
        }

        if (bufferSize <= 0 || bufferSize > 10_000_000) {
            throw new ArgumentException("Invalid buffer size");
        }

        return new byte[bufferSize];
    }
}

Test with Edge Cases and Boundary Values

#include <assert.h>
#include <limits.h>

void test_safe_conversions() {
    int result;

    // Test 1: Value within range - should succeed
    assert(safe_long_to_int(100L, &result) == true);
    assert(result == 100);
    printf("✓ Test 1: Normal value\n");

    // Test 2: Value too large - should fail
    assert(safe_long_to_int((long)INT_MAX + 1, &result) == false);
    printf("✓ Test 2: Overflow detection\n");

    // Test 3: Value too small - should fail
    assert(safe_long_to_int((long)INT_MIN - 1, &result) == false);
    printf("✓ Test 3: Underflow detection\n");

    // Test 4: Boundary values - should succeed
    assert(safe_long_to_int(INT_MAX, &result) == true);
    assert(result == INT_MAX);
    assert(safe_long_to_int(INT_MIN, &result) == true);
    assert(result == INT_MIN);
    printf("✓ Test 4: Boundary values\n");

    // Test 5: Zero - should succeed
    assert(safe_long_to_int(0L, &result) == true);
    assert(result == 0);
    printf("✓ Test 5: Zero\n");

    // Test 6: Negative to unsigned conversion
    unsigned int u;
    int negative = -1;
    // This should be rejected in safe code
    printf("✓ Test 6: Negative to unsigned (should reject)\n");
}

void test_mixed_types() {
    // Test signed/unsigned comparison issue
    unsigned int u = 10;
    int s = -5;

    // Unsafe comparison (s gets promoted to unsigned)
    int unsafe_result = (s < u);  // TRUE but for wrong reason

    // Safe comparison
    int safe_result = (s < 0 || (unsigned int)s < u);

    printf("Unsafe: %d, Safe: %d\n", unsafe_result, safe_result);
}

int main() {
    test_safe_conversions();
    test_mixed_types();
    printf("All integer coercion tests passed!\n");
    return 0;
}

Common Vulnerable Patterns

Implicit Large-to-Small Type Conversion

#include <stdlib.h>

// Dangerous: implicit conversion loses data
void allocate_buffer(long size) {
    // Attack: size = 0x100000001 (4GB + 1)
    int buffer_size = size;  // Implicitly truncates to 1

    char *buffer = malloc(buffer_size);  // Allocates 1 byte!
}

Signed/Unsigned Type Mismatch in Comparison

#include <string.h>

// Dangerous: signed/unsigned mismatch
int copy_data(char *dest, int dest_size, char *src) {
    size_t src_len = strlen(src);  // Returns size_t (unsigned)

    // Comparison with signed int - can have issues
    if (src_len < dest_size) {  // If dest_size is negative?
        memcpy(dest, src, src_len);
    }
}

Function Parameter Type Truncation

// Dangerous: function parameter coercion
void process_count(unsigned short count) {
    // ...
}

int main() {
    int user_input = 100000;  // Larger than USHRT_MAX
    process_count(user_input);  // Truncated to 34464
}

## Secure Patterns

### Validated Type Narrowing with Helper Functions (C)

```c
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>

bool safe_long_to_int(long value, int *result) {
    if (value < INT_MIN || value > INT_MAX) {
        return false;
    }
    *result = (int)value;  // Explicit cast
    return true;
}

void allocate_buffer_safe(long size) {
    int buffer_size;

    // Validate before conversion
    if (!safe_long_to_int(size, &buffer_size)) {
        fprintf(stderr, "Size out of range\n");
        return;
    }

    if (buffer_size <= 0 || buffer_size > 1000000) {
        fprintf(stderr, "Invalid buffer size\n");
        return;
    }

    char *buffer = malloc(buffer_size);
}

Why this works: Validating that the value fits within the target type's range before casting prevents truncation. Using explicit casts makes the conversion visible and intentional.

Consistent Type Usage for Comparisons (C)

#include <string.h>

int copy_data_safe(char *dest, size_t dest_size, char *src) {
    size_t src_len = strlen(src);

    // Compare same types
    if (src_len >= dest_size) {
        return -1;
    }

    memcpy(dest, src, src_len);
    dest[src_len] = '\0';
    return 0;
}

Why this works: Using consistent types (both size_t) for comparisons avoids implicit promotion issues. Signed/unsigned comparison can cause negative values to be interpreted as large positive numbers.

Pre-Validation for Function Parameters (C)

#include <limits.h>

void process_count_safe(unsigned short count) {
    // ...
}

int main_safe() {
    int user_input = 100000;

    // Validate before calling
    if (user_input < 0 || user_input > USHRT_MAX) {
        fprintf(stderr, "Value out of range\n");
        return 1;
    }

    process_count_safe((unsigned short)user_input);
    return 0;
}

Why this works: Validating that the value fits in the target type before passing it to a function prevents truncation. Using explicit casts documents the intentional type conversion.

Template-Based Safe Casting (C++)

#include <limits>
#include <stdexcept>
#include <type_traits>

template<typename Target, typename Source>
Target safe_cast(Source value) {
    if (value < std::numeric_limits<Target>::min() ||
        value > std::numeric_limits<Target>::max()) {
        throw std::overflow_error("Value out of range");
    }
    return static_cast<Target>(value);
}

void allocate_buffer_cpp(int64_t size) {
    // Explicit conversion with validation
    int32_t buffer_size = safe_cast<int32_t>(size);

    if (buffer_size <= 0 || buffer_size > 1000000) {
        throw std::invalid_argument("Invalid buffer size");
    }

    auto buffer = std::make_unique<char[]>(buffer_size);
}

Why this works: Template-based casting provides compile-time type safety and runtime range validation. Using std::numeric_limits ensures checks are correct for all target types.

Exception-Based Validation (Java)

public class SafeCasting {
    public static int safeLongToInt(long value) {
        if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
            throw new IllegalArgumentException(
                "Value " + value + " out of int range");
        }
        return (int) value;
    }

    public static byte[] allocateBuffer(long size) {
        // Validate and convert
        int bufferSize = safeLongToInt(size);

        if (bufferSize <= 0 || bufferSize > 10_000_000) {
            throw new IllegalArgumentException("Invalid buffer size");
        }

        return new byte[bufferSize];
    }

    public static short safeIntToShort(int value) {
        if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
            throw new IllegalArgumentException("Value out of short range");
        }
        return (short) value;
    }
}

Why this works: Throwing exceptions on out-of-range conversions prevents silent truncation. Using wrapper methods centralizes validation logic and makes the code self-documenting.

Security Checklist

  • All integer conversions are explicit (no implicit casts)
  • Range validation before narrowing conversions (large to small types)
  • Consistent use of signed or unsigned in expressions
  • Compiler warnings enabled (-Wconversion, -Wsign-conversion)
  • Static analysis tools identify no coercion errors
  • Tests cover: normal values, boundary values (MAX/MIN), overflow cases
  • Negative to unsigned conversions validated
  • 64-bit to 32-bit conversions checked

Additional Resources