Incorrect Conversion between Numeric Types

Description

Incorrect Conversion between Numeric Types is a vulnerability where a program converts values between different numeric types in a way that produces unexpected or incorrect results. This includes conversions between signed and unsigned integers, conversions that truncate values (e.g., long to int), floating-point to integer conversions that lose precision, and implicit conversions that change value semantics. When the converted value is used in sensitive operations like memory allocation, array indexing, or security decisions, the incorrect value can lead to serious security vulnerabilities.

Risk

Incorrect numeric conversions create diverse security risks depending on context. Signed-to-unsigned conversion of negative values produces large positive values, potentially causing buffer overflows when used as sizes. Truncation of large values to smaller types loses high-order bits, resulting in unexpectedly small values. Floating-point to integer conversion loses fractional parts, causing precision-dependent security checks to fail. Platform-dependent type sizes (int being 32-bit vs 64-bit) create portability bugs that manifest as security vulnerabilities on different architectures. These bugs are often subtle and may only trigger with carefully crafted input values.

Solution

Avoid type conversions when possible by using consistent types throughout calculations. When conversion is necessary, explicitly validate that the value fits within the target type's range before converting. Use safe conversion functions or macros that check for overflow and underflow. Be aware of implicit conversions in expressions and use explicit casts to make intent clear. Test on multiple platforms with different type sizes. Use static analysis tools that detect dangerous conversions. Follow CERT guidelines for secure integer operations.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Modify Application Data - Incorrect values from bad conversions can corrupt data or produce wrong calculations.
AvailabilityScope: Availability

DoS: Crash, Exit, or Restart - Unexpected values can cause crashes, especially when used for memory operations.
OtherScope: Other

Quality Degradation - Programs produce incorrect results when conversions change values unexpectedly.

Example Code

Vulnerable Code

// Vulnerable: Signed to unsigned conversion
#include <stdlib.h>
#include <string.h>

void vulnerable_copy(char* dest, char* src, int length) {
    // Vulnerable: If length is negative, unsigned conversion makes it huge
    // memcpy will read/write massive amounts of memory
    memcpy(dest, src, (size_t)length);  // -1 becomes SIZE_MAX!
}

unsigned int vulnerable_read_size() {
    int size = read_from_network();

    if (size == -1) {
        // Error condition returns -1
        return size;  // Converts -1 to 4,294,967,295 (UINT_MAX)!
    }

    return size;
}

// Vulnerable: Truncation from larger to smaller type
void vulnerable_truncation(size_t large_size) {
    // On 64-bit system: size_t is 64-bit, int is 32-bit
    int small_size = (int)large_size;  // Truncates to 32 bits

    // If large_size was 0x100000010 (4GB + 16 bytes)
    // small_size becomes just 16!
    char* buffer = malloc(small_size);
    // Allocates 16 bytes instead of 4GB

    // Later code uses large_size to write data - buffer overflow!
    fill_buffer(buffer, large_size);
}

// Vulnerable: Integer width assumptions
void vulnerable_width() {
    long value = get_user_input();  // Assume this could be large

    // On some platforms, int == long (both 32-bit)
    // On others, long is 64-bit and int is 32-bit
    int truncated = (int)value;  // Truncation on 64-bit platforms

    if (truncated > 0 && truncated < MAX_SIZE) {
        // This check passes even if original value was huge
        allocate(truncated);
    }
}
// Vulnerable: Java floating-point to integer
public class VulnerableConversion {

    // Vulnerable: Precision loss
    public int calculateDiscount(float price, float discountPercent) {
        float discountAmount = price * discountPercent / 100;

        // Vulnerable: Truncation toward zero
        int cents = (int)(discountAmount * 100);

        // $1.999 becomes 199 cents, not 200
        // Repeated calculations accumulate errors
        return cents;
    }

    // Vulnerable: Overflow in conversion
    public int vulnerableArrayIndex(long userIndex) {
        // Vulnerable: Long to int truncation
        int index = (int)userIndex;

        // If userIndex = 0x100000000L (4GB), index becomes 0
        // Bypasses bounds checking if based on original long
        return array[index];
    }
}
<?php
// Vulnerable: PHP type juggling in numeric conversion
function vulnerable_php_conversion($value) {
    // PHP automatically converts types
    $floatVal = 1.8345;
    $intVal = 3;

    // Vulnerable: Float truncated, not rounded
    $result = (int)$floatVal + $intVal;
    // Result is 4, not 5 as might be expected

    // Vulnerable: String to number conversion
    $userInput = "10 apples";
    $count = (int)$userInput;  // Becomes 10, ignoring " apples"

    $malicious = "0x1A";
    $num = (int)$malicious;  // Becomes 0 in PHP 7+, was 26 in PHP 5

    return $count;
}
// Vulnerable: Sign extension issues
short vulnerable_sign_extension(short value) {
    // If value is negative (e.g., -1 = 0xFFFF as 16-bit)
    // Extending to 32-bit int: 0xFFFFFFFF (-1)
    // Then casting to unsigned: 4294967295
    unsigned int extended = value;

    return extended;  // Huge value from small negative number
}

// Vulnerable: Platform-dependent behavior
void vulnerable_platform() {
    size_t large = SIZE_MAX;  // Maximum size_t value

    // On 32-bit: SIZE_MAX = 0xFFFFFFFF (4GB - 1)
    // On 64-bit: SIZE_MAX = 0xFFFFFFFFFFFFFFFF (18 exabytes - 1)

    unsigned int truncated = (unsigned int)large;
    // On 64-bit, this truncates to 0xFFFFFFFF

    if (truncated == large) {
        // True on 32-bit, false on 64-bit
        // Platform-dependent behavior!
    }
}

Fixed Code

// Fixed: Safe signed to unsigned conversion
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>

int safe_copy(char* dest, size_t dest_size, char* src, int length) {
    // Fixed: Validate length before conversion
    if (length < 0) {
        return -1;  // Error: negative length
    }

    if ((size_t)length > dest_size) {
        return -1;  // Error: would overflow destination
    }

    memcpy(dest, src, (size_t)length);
    return 0;
}

// Fixed: Safe integer return with proper error handling
#define SIZE_ERROR ((size_t)-1)  // Define error value

size_t safe_read_size() {
    int size = read_from_network();

    if (size < 0) {
        // Return distinct error value
        return SIZE_ERROR;
    }

    return (size_t)size;
}

// Fixed: Check before truncation
int safe_truncation(size_t large_size) {
    // Fixed: Verify value fits in target type
    if (large_size > INT_MAX) {
        return -1;  // Error: value too large
    }

    int small_size = (int)large_size;  // Safe after check

    char* buffer = malloc(small_size);
    if (buffer == NULL) return -1;

    fill_buffer(buffer, small_size);  // Use same variable
    free(buffer);
    return 0;
}

// Fixed: Explicit size types
int safe_width(int64_t value) {
    // Fixed: Use explicit-width types
    if (value < 0 || value > INT32_MAX) {
        return -1;
    }

    int32_t bounded = (int32_t)value;

    if (bounded > 0 && bounded < MAX_SIZE) {
        allocate(bounded);
    }
    return 0;
}

// Fixed: Safe conversion utility functions
// Convert size_t to int safely
int size_to_int(size_t value, int* result) {
    if (value > INT_MAX) {
        return -1;  // Would overflow
    }
    *result = (int)value;
    return 0;
}

// Convert int64_t to int32_t safely
int i64_to_i32(int64_t value, int32_t* result) {
    if (value < INT32_MIN || value > INT32_MAX) {
        return -1;  // Out of range
    }
    *result = (int32_t)value;
    return 0;
}

// Convert signed to unsigned safely
int signed_to_unsigned(int value, unsigned int* result) {
    if (value < 0) {
        return -1;  // Negative values invalid for unsigned
    }
    *result = (unsigned int)value;
    return 0;
}
// Fixed: Java safe conversions
public class SafeConversion {

    // Fixed: Proper rounding for currency
    public int calculateDiscountSafe(double price, double discountPercent) {
        double discountAmount = price * discountPercent / 100;

        // Fixed: Use proper rounding
        int cents = (int)Math.round(discountAmount * 100);

        return cents;
    }

    // Fixed: Bounds checking before conversion
    public int safeArrayIndex(long userIndex) {
        // Fixed: Validate range before conversion
        if (userIndex < 0 || userIndex >= array.length) {
            throw new IndexOutOfBoundsException("Invalid index: " + userIndex);
        }

        // Safe: we verified it fits
        int index = (int)userIndex;
        return array[index];
    }

    // Fixed: Using Math.toIntExact (Java 8+)
    public int safeToInt(long value) {
        // Throws ArithmeticException if value doesn't fit in int
        return Math.toIntExact(value);
    }
}
<?php
// Fixed: Explicit PHP conversions with validation
function safe_php_conversion($value) {
    // Fixed: Explicit rounding
    $floatVal = 1.8345;
    $intVal = 3;
    $result = (int)round($floatVal) + $intVal;  // 2 + 3 = 5

    // Fixed: Validate numeric input
    $userInput = "10 apples";
    if (!is_numeric($userInput)) {
        throw new InvalidArgumentException("Not a valid number");
    }
    $count = (int)$userInput;

    // Fixed: Use intval with explicit base
    $hexInput = "1A";
    $num = intval($hexInput, 16);  // Explicitly parse as hex: 26

    return $count;
}

// Fixed: Type-safe comparison
function safe_compare($a, $b) {
    // Fixed: Use identical comparison when types matter
    if ($a === $b) {
        // Same value AND same type
    }

    // Fixed: Explicit type casting with validation
    $intA = filter_var($a, FILTER_VALIDATE_INT);
    $intB = filter_var($b, FILTER_VALIDATE_INT);

    if ($intA === false || $intB === false) {
        throw new InvalidArgumentException("Invalid integers");
    }

    return $intA === $intB;
}
// Fixed: Safe sign extension
int safe_sign_extension(short value, unsigned int* result) {
    // Fixed: Check for negative before unsigned conversion
    if (value < 0) {
        return -1;  // Error: negative values not allowed
    }

    *result = (unsigned int)value;
    return 0;
}

// Fixed: Platform-independent code
#include <inttypes.h>

void safe_platform() {
    // Fixed: Use explicit-width types
    uint64_t large = UINT64_MAX;

    // Explicitly handle on all platforms
    if (large > UINT32_MAX) {
        // Handle large value case
        process_large(large);
    } else {
        uint32_t small = (uint32_t)large;
        process_small(small);
    }
}

CVE Examples

  • CVE-2022-2639: Integer coercion error preventing proper error indication, leading to out-of-bounds write.
  • CVE-2021-43537: 64-bit to 32-bit integer truncation causing heap buffer overflow.
  • CVE-2007-4268: Sign extension error triggering heap overflow.
  • CVE-2009-0231: Integer truncation causing heap-based buffer overflow.

References

  1. MITRE Corporation. "CWE-681: Incorrect Conversion between Numeric Types." https://cwe.mitre.org/data/definitions/681.html
  2. CERT C Coding Standard. "INT31-C. Ensure that integer conversions do not result in lost or misinterpreted data."
  3. CERT C Coding Standard. "INT02-C. Understand integer conversion rules."