Unexpected Sign Extension
Description
Unexpected Sign Extension occurs when a signed integer value is converted to a larger integer type, and the sign bit is extended to fill the additional bits. When a negative signed value (with the high bit set) is extended to a larger type, it becomes a very large positive number if subsequently interpreted as unsigned, or remains a large negative number. This can produce unexpected values that bypass security checks, cause buffer overflows, or lead to other memory corruption issues when the extended value is used for sizes, indices, or loop counters.
Risk
Sign extension vulnerabilities are particularly dangerous because they can silently transform small negative values into enormous positive values. When a negative char (-1) is sign-extended to an int and then cast to size_t, it becomes a value near SIZE_MAX. If this value is used for buffer allocation or as a loop bound, catastrophic buffer overflows result. These vulnerabilities are subtle and easy to miss during code review. Real-world exploits have demonstrated that sign extension bugs in document parsers, protocol handlers, and media libraries can enable arbitrary code execution.
Solution
Avoid using signed integers for values that should never be negative, such as sizes, lengths, and indices. When converting between integer types of different sizes, explicitly check value ranges before conversion. Use explicit casts that make the intent clear. Validate that signed values are non-negative before using them in size calculations. Be particularly careful when char values (which may be signed by default) are used in array indexing. Use unsigned char or explicitly cast to unsigned when processing byte data. Enable compiler warnings for implicit conversions between signed and unsigned types.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Memory Corruption Sign-extended values used as sizes or indices cause massive buffer overflows. |
| Access Control | Scope: Security Bypass Sign extension can cause negative values to pass length checks when interpreted as large positive unsigned values. |
| Availability | Scope: Denial of Service Massive memory allocations from sign-extended sizes cause resource exhaustion. |
Example Code + Solution Code
Vulnerable Code
#include <stdlib.h>
#include <string.h>
// VULNERABLE: Sign extension from char to int to size_t
void process_byte(char len_byte) {
// If len_byte is -1 (0xFF), sign extension makes it
// 0xFFFFFFFF (huge positive) when cast to size_t
size_t length = len_byte;
char *buffer = malloc(length); // Enormous allocation
// ...
}
// VULNERABLE: Sign extension bypasses length check
int copy_data(char *dest, char *src, short src_len) {
// src_len could be negative
if (src_len > MAX_SIZE) { // Negative passes this check!
return -1;
}
// Sign extended to size_t, becomes huge positive
memcpy(dest, src, src_len); // Massive overflow
return 0;
}
// VULNERABLE: Signed char as array index
char lookup_table[256];
char get_lookup(char index) {
// If index is negative (e.g., -50), this reads before array
// When converted to int for indexing, becomes negative offset
return lookup_table[index]; // Under-read!
}
// VULNERABLE: Pascal-style string length
void copy_pascal_string(char *dest, char *src) {
// First byte is length (signed char)
char len = *src; // Could be negative
// Sign extension: len = -1 becomes 0xFFFFFFFF
memcpy(dest, src + 1, len); // Massive overflow
}
Fixed Code
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
// SAFE: Use unsigned type, validate range
void process_byte_safe(unsigned char len_byte) {
// unsigned char: 0-255, no sign extension
size_t length = len_byte;
if (length == 0 || length > MAX_LENGTH) {
return;
}
char *buffer = malloc(length);
// ...
}
// SAFE: Validate signed value before use
int copy_data_safe(char *dest, size_t dest_size,
const char *src, int16_t src_len) {
// Check for negative AND upper bound
if (src_len < 0 || (size_t)src_len > MAX_SIZE) {
return -1;
}
// After validation, cast is safe
size_t copy_len = (size_t)src_len;
if (copy_len > dest_size) {
return -1;
}
memcpy(dest, src, copy_len);
return 0;
}
// SAFE: Use unsigned char for byte indexing
char lookup_table[256];
char get_lookup_safe(unsigned char index) {
// unsigned char: always 0-255, valid index
return lookup_table[index];
}
// Alternative: explicit masking
char get_lookup_masked(char index) {
// Mask to ensure positive index
unsigned char safe_index = (unsigned char)index;
return lookup_table[safe_index];
}
// SAFE: Validate Pascal string length
int copy_pascal_string_safe(char *dest, size_t dest_size,
const unsigned char *src, size_t src_size) {
if (src_size == 0) return -1;
// Read length as unsigned
unsigned char len = src[0];
// Validate length against both source and destination
if (len > src_size - 1 || len > dest_size) {
return -1;
}
memcpy(dest, src + 1, len);
dest[len] = '\0';
return 0;
}
Exploited in the Wild
SoftMaker Office TextMaker (SoftMaker, 2021)
CVE-2021-40411 is a sign extension vulnerability in SoftMaker Office 2021's TextMaker document parsing. A specially crafted document causes the parser to sign-extend a loop termination length, resulting in the loop index being used to write outside heap buffer bounds.
CODESYS Industrial Control (CODESYS, Multiple)
Multiple CODESYS products contained sign extension vulnerabilities where remote attackers could craft requests causing unexpected sign extension, resulting in denial of service or memory overwrite in industrial control systems.
Historical: BSD Network Stack (BSD, 1990s)
Classic sign extension vulnerabilities in BSD network stack implementations affected TCP/IP processing and socket operations, demonstrating the long history of this vulnerability class.
Tools to test/exploit
-
UBSan — detects implicit conversions that change value.
-
Coverity — static analysis identifying sign extension issues.
-
PVS-Studio — detects suspicious type conversions and sign extension.
CVE Examples
-
CVE-2021-40411 — SoftMaker Office sign extension leading to heap overflow.
-
CVE-2022-33260 — CODESYS sign extension causing memory corruption.
-
CVE-2017-7308 — Linux kernel packet socket sign extension.
References
-
MITRE. "CWE-194: Unexpected Sign Extension." https://cwe.mitre.org/data/definitions/194.html
-
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