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

ImpactDetails
IntegrityScope: 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

  1. MITRE Corporation. "CWE-394: Unexpected Status Code or Return Value." https://cwe.mitre.org/data/definitions/394.html