Signed to Unsigned Conversion Error

Description

Signed to Unsigned Conversion Error occurs when a signed integer value is converted to an unsigned integer type. When the signed value is negative, the conversion produces a very large positive unsigned value (typically near the maximum value for that unsigned type). For example, converting -1 (signed int) to an unsigned 32-bit integer produces 4294967295. This is particularly dangerous when the converted value is used for memory allocation sizes, buffer lengths, or loop counters, as the unexpectedly large value can cause buffer overflows or resource exhaustion.

Risk

Signed to unsigned conversion errors are extremely dangerous because they can transform small negative values (often error codes like -1) into enormous positive values. Functions returning -1 on error are common in C/C++ APIs. If these error return values are not checked before being used as sizes in malloc(), memcpy(), or array accesses, the result is catastrophic buffer overflow or memory exhaustion. Attackers specifically craft inputs to trigger error conditions that produce negative values, knowing the conversion will create exploitable overflow conditions.

Solution

Always check return values from functions that may return negative error codes before using them as unsigned sizes. Use size_t or unsigned types for variables that should never be negative. Add explicit range validation before type conversions. Enable compiler warnings for implicit signed/unsigned conversions (-Wsign-conversion in GCC/Clang). Consider using separate error handling mechanisms rather than sentinel values. Apply static analysis to identify dangerous conversion patterns.

Common Consequences

ImpactDetails
IntegrityScope: Memory Corruption

Negative values converted to large unsigned values cause massive buffer overflows when used as sizes.
AvailabilityScope: Resource Exhaustion

Large converted values can cause memory exhaustion through enormous allocations.
Access ControlScope: Code Execution

Buffer overflows resulting from conversion errors enable arbitrary code execution.

Example Code + Solution Code

Vulnerable Code

#include <stdlib.h>
#include <string.h>

// VULNERABLE: Function returns -1 on error
int get_data_size(int fd) {
    int size = read_header(fd);
    if (size < 0) return -1;  // Error return
    return size;
}

void process_file(int fd) {
    int size = get_data_size(fd);
    // Missing error check!

    // size (-1) converted to size_t becomes huge positive
    char *buffer = malloc(size);  // Allocates 4GB or fails

    read(fd, buffer, size);  // Overflow!
}

// VULNERABLE: memcpy with signed size
void copy_data(char *dest, char *src, int count) {
    // If count is negative from upstream error...
    memcpy(dest, src, count);  // count converted to size_t, huge value
}

// VULNERABLE: Array index with error value
#define ERROR_VALUE -1
int data_array[1000];

void store_result(int index, int value) {
    // index might be ERROR_VALUE from failed operation
    data_array[index] = value;  // Underflow: accesses before array
}

Fixed Code

#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

// SAFE: Check error return before use
int get_data_size(int fd) {
    int size = read_header(fd);
    if (size < 0) return -1;
    return size;
}

bool process_file_safe(int fd) {
    int size = get_data_size(fd);

    // Check for error return!
    if (size < 0) {
        return false;  // Handle error
    }

    // Additional sanity check
    if (size > MAX_BUFFER_SIZE) {
        return false;
    }

    // Now safe to use as size
    char *buffer = malloc((size_t)size);
    if (!buffer) return false;

    ssize_t bytes_read = read(fd, buffer, (size_t)size);
    free(buffer);

    return (bytes_read == size);
}

// SAFE: Validate signed value before conversion
bool copy_data_safe(char *dest, size_t dest_size,
                    const char *src, int count) {
    // Reject negative count
    if (count < 0) {
        return false;
    }

    // Check against destination size
    if ((size_t)count > dest_size) {
        return false;
    }

    memcpy(dest, src, (size_t)count);
    return true;
}

// SAFE: Use separate error handling
typedef struct {
    bool success;
    size_t value;
} SizeResult;

SizeResult get_size_safe(int fd) {
    SizeResult result = {false, 0};

    int size = read_header(fd);
    if (size >= 0) {
        result.success = true;
        result.value = (size_t)size;
    }

    return result;
}

Exploited in the Wild

Okio GzipSource Denial of Service (Okio, 2023)

CVE-2023-3635 in Okio library where GzipSource does not properly handle exceptions when parsing malformed gzip buffers, leading to signed-to-unsigned conversion errors that cause denial of service.

Juniper Layer 2 Control Protocol (Juniper, 2025)

CVE-2025-30646 is a signed to unsigned conversion error in Juniper's Layer 2 Control Protocol (L2CP) implementation affecting network infrastructure.

Multiple System Libraries (Various, Historical)

Many historical vulnerabilities in libc implementations, OpenSSL, and other system libraries stemmed from unchecked conversions of error return values to unsigned sizes.


Tools to test/exploit

  • UBSan — detects implicit type conversions that change value.

  • Coverity — static analysis identifying dangerous signed/unsigned conversions.

  • PVS-Studio — detects suspicious type conversions.


CVE Examples

  • CVE-2023-3635 — Okio GzipSource signed to unsigned conversion DoS.

  • CVE-2025-30646 — Juniper L2CP signed to unsigned conversion error.

  • CVE-2021-21220 — Chrome V8 type confusion involving integer conversion.


References

  1. MITRE. "CWE-195: Signed to Unsigned Conversion Error." https://cwe.mitre.org/data/definitions/195.html

  2. CERT. "INT31-C. Ensure that integer conversions do not result in lost or misinterpreted data." https://wiki.sei.cmu.edu/confluence/display/c/INT31-C