Unexpected Status Code or Return Value
Description
Unexpected Status Code or Return Value is a vulnerability that occurs when software fails to adequately verify when a function or operation returns a legitimate value that the product did not anticipate receiving. While related to unchecked return values (CWE-252), this weakness specifically involves cases where the code checks some return values but fails to handle unexpected but valid values. The program may assume a limited set of possible returns when the API can actually return additional legitimate status codes.
Risk
Failing to handle unexpected but valid return values can cause programs to enter undefined states. Network functions like recv() may return fewer bytes than expected or special values indicating different conditions. DNS lookups may return unexpected results that bypass access controls. System calls may behave differently on different platforms or hardware, returning values not handled by the code. When these unexpected values aren't handled, programs may enter infinite loops, leak memory, leave data uninitialized, or bypass security checks. The vulnerability is particularly dangerous because the code appears to check return values but misses edge cases.
Solution
Review API documentation thoroughly to understand all possible return values, not just the common ones. Handle all documented return values explicitly, including edge cases and platform-specific behaviors. Use defensive programming techniques that treat unexpected values as errors rather than assuming success. Implement proper bounds checking when functions return counts or sizes. Test with fuzzing and edge case testing to discover code paths that don't handle unusual returns. Consider using wrapper functions that normalize return values across platforms.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity, Other Unexpected State, Alter Execution Logic - The program may enter unintended states or execute unintended logic when unexpected values are received. |
Example Code
Vulnerable Code
// Vulnerable: Doesn't handle all recv() return values
void vulnerable_receive_data(int sock, char *buffer, size_t expected) {
ssize_t received = recv(sock, buffer, expected, 0);
// Vulnerable: Only checks for error (-1)
if (received == -1) {
perror("recv failed");
return;
}
// Vulnerable: Doesn't handle 0 (connection closed)
// Vulnerable: Doesn't handle partial read (received < expected)
// Proceeds as if all data received
process_data(buffer, expected); // May use uninitialized memory!
}
// Vulnerable: Doesn't handle unexpected read() returns
void vulnerable_read_file(int fd, char *buffer, size_t size) {
ssize_t bytes_read = read(fd, buffer, size);
if (bytes_read > 0) {
// Vulnerable: Assumes all bytes read
buffer[size - 1] = '\0'; // May leave data uninitialized
}
}
// Vulnerable: recvfrom unexpected return causes loop exit
void vulnerable_server_loop(int sock) {
char buffer[1024];
struct sockaddr_in client;
socklen_t client_len = sizeof(client);
while (1) {
ssize_t received = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&client, &client_len);
// Vulnerable: Zero-length packet causes unexpected behavior
if (received <= 0) {
// Treats 0 same as error, may exit loop unexpectedly
break;
}
handle_request(buffer, received, &client);
}
}
# Vulnerable: DNS lookup unexpected result
def vulnerable_access_check(hostname):
try:
ip = socket.gethostbyname(hostname)
except socket.gaierror:
# Blocked - hostname doesn't resolve
return False
# Vulnerable: Doesn't handle empty result or unexpected format
if ip is None:
return False
# Vulnerable: Reverse lookup may return None
reverse = socket.gethostbyaddr(ip)[0]
# Vulnerable: If reverse lookup fails/returns unexpected value,
# this check is bypassed
if reverse != expected_hostname:
return False
return True
# Vulnerable: Partial read not handled
def vulnerable_read_message(sock):
# Expects exactly 4 bytes for message length
length_data = sock.recv(4)
# Vulnerable: recv() may return fewer than 4 bytes
length = struct.unpack('!I', length_data)[0] # May fail or give wrong value!
# Read message
message = sock.recv(length) # May also be partial!
return message
// Vulnerable: InputStream.read() partial reads
public class VulnerableReader {
public byte[] readExact(InputStream in, int size) throws IOException {
byte[] buffer = new byte[size];
// Vulnerable: read() may return fewer bytes than requested
int bytesRead = in.read(buffer);
if (bytesRead == -1) {
throw new EOFException("Unexpected end of stream");
}
// Vulnerable: Assumes all bytes read
return buffer; // May contain uninitialized data!
}
}
Fixed Code
// Fixed: Handle all recv() return values
int secure_receive_data(int sock, char *buffer, size_t expected) {
size_t total_received = 0;
while (total_received < expected) {
ssize_t received = recv(sock, buffer + total_received,
expected - total_received, 0);
// Fixed: Handle error
if (received == -1) {
perror("recv failed");
return -1;
}
// Fixed: Handle connection closed
if (received == 0) {
fprintf(stderr, "Connection closed by peer\n");
return -2;
}
// Fixed: Handle partial read by looping
total_received += received;
}
process_data(buffer, expected);
return 0;
}
// Fixed: Handle all read() scenarios
int secure_read_file(int fd, char *buffer, size_t size) {
size_t total_read = 0;
while (total_read < size) {
ssize_t bytes_read = read(fd, buffer + total_read, size - total_read);
if (bytes_read == -1) {
if (errno == EINTR) {
continue; // Interrupted, retry
}
return -1; // Actual error
}
if (bytes_read == 0) {
// EOF reached before expected bytes
return -2;
}
total_read += bytes_read;
}
return 0; // Success
}
// Fixed: Handle zero-length packets properly
void secure_server_loop(int sock) {
char buffer[1024];
struct sockaddr_in client;
socklen_t client_len;
while (1) {
client_len = sizeof(client); // Reset each time
ssize_t received = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&client, &client_len);
if (received == -1) {
if (errno == EINTR) {
continue; // Interrupted, retry
}
perror("recvfrom failed");
break; // Actual error
}
// Fixed: Zero-length packet is valid, just skip processing
if (received == 0) {
continue;
}
handle_request(buffer, received, &client);
}
}
# Fixed: Handle all DNS lookup results
def secure_access_check(hostname):
try:
ip = socket.gethostbyname(hostname)
except socket.gaierror as e:
logger.warning(f"DNS lookup failed for {hostname}: {e}")
return False
if not ip:
return False
try:
reverse_result = socket.gethostbyaddr(ip)
reverse = reverse_result[0] if reverse_result else None
except socket.herror:
# Fixed: Treat reverse lookup failure as access denied
logger.warning(f"Reverse DNS failed for {ip}")
return False
if reverse is None or reverse != expected_hostname:
return False
return True
# Fixed: Handle partial reads
def secure_read_message(sock):
# Read exactly 4 bytes for length
length_data = secure_recv_exact(sock, 4)
if length_data is None:
raise ConnectionError("Failed to read message length")
length = struct.unpack('!I', length_data)[0]
# Validate length
if length > MAX_MESSAGE_SIZE:
raise ValueError(f"Message too large: {length}")
# Read exact message length
message = secure_recv_exact(sock, length)
if message is None:
raise ConnectionError("Failed to read message body")
return message
def secure_recv_exact(sock, size):
"""Receive exactly 'size' bytes."""
buffer = b''
while len(buffer) < size:
chunk = sock.recv(size - len(buffer))
if not chunk:
return None # Connection closed
buffer += chunk
return buffer
// Fixed: Handle partial reads from InputStream
public class SecureReader {
public byte[] readExact(InputStream in, int size) throws IOException {
byte[] buffer = new byte[size];
int totalRead = 0;
while (totalRead < size) {
int bytesRead = in.read(buffer, totalRead, size - totalRead);
// Fixed: Handle EOF
if (bytesRead == -1) {
throw new EOFException(
"Expected " + size + " bytes, got " + totalRead);
}
totalRead += bytesRead;
}
return buffer;
}
// Alternative: Use DataInputStream which handles this
public byte[] readExactWithDataStream(InputStream in, int size)
throws IOException {
DataInputStream dis = new DataInputStream(in);
byte[] buffer = new byte[size];
dis.readFully(buffer); // Throws EOFException if not enough bytes
return buffer;
}
}
CVE Examples
- CVE-2004-1395 — Zero byte and other length packets cause recvfrom to produce unexpected return code, exiting server's listening loop.
- CVE-2002-2124 — Unchecked recv() return code triggers infinite loop.
- CVE-2005-1858 — read() returning fewer bytes than expected leaves memory uncleared.
- CVE-2000-0536 — Access restriction bypass when DNS reverse lookup returns no hostname.
References
- MITRE Corporation. "CWE-394: Unexpected Status Code or Return Value." https://cwe.mitre.org/data/definitions/394.html