Safeguard
Application Security•

CWE-394 Unexpected Status Code or Return Value: Causes and Fixes

Functions can return legitimate values your code never planned for. CWE-394 explained with four real CVEs on recv, read and null returns, plus the fix pattern.

Safeguard Security Team
4 min read

A function's documentation lists the values it can return. Real programs often handle only the ones the author expected. CWE-394 is the weakness where the product does not properly check when a function or operation returns a value that is legitimate for the function, but not expected by the product.

The return value is not wrong here (that is CWE-393). The caller simply never planned for it. It is most common in C and other languages where errors travel through return codes.

How it happens

Typical shapes are a read that returns fewer bytes than requested, a recv that returns zero or negative, a lookup that returns null, or an enum with more members than the switch handles.

char buf[256];
int n = recv(sock, buf, sizeof(buf), 0);
process(buf, n);               // n may be 0, negative, or short

The record lists four real cases:

  • CVE-2004-1395: certain packets, such as zero byte and other lengths, cause a recvfrom call to produce an unexpected return code that makes a server's listening loop exit.
  • CVE-2002-2124: an unchecked return code from recv() leads to an infinite loop.
  • CVE-2005-2553: a kernel function does not properly handle a null returned by a function call, causing it to call another function it should not.
  • CVE-2005-1858: memory is not properly cleared when a read() call returns fewer bytes than expected.

In each case the attacker only has to send input that makes the call return the less common value.

What an attacker gains

MITRE lists an integrity impact: an unexpected state, with altered execution logic. The examples show the forms this takes in practice. A server stops listening, a loop never ends, a wrong code path runs, or stale data is left in memory. The end result is often denial of service, and sometimes more.

How to find it

MITRE says automated static analysis (SAST) can find some instances. Practical checks:

  • Enable warnings for ignored return values and use annotations such as warn-unused-result on your own wrappers.
  • Review every call to I/O functions and ask what happens at zero, at negative, and at fewer than requested.
  • Fuzz network and file parsers with empty, truncated and oversized inputs.
  • Read every switch over a return code and confirm it has a default branch that fails safely.

How to fix it

The record lists no mitigations, so these are steps that follow from the weakness:

  1. Check the return value of every call that can fail or return a partial result.
  2. Handle each documented value on purpose: success, partial, end of stream, error.
  3. Add a safe default branch. Unknown means fail.
  4. Loop on partial reads and writes until you have what you need or a real error occurs.
size_t got = 0;
while (got < need) {
    ssize_t n = recv(sock, buf + got, need - got, 0);
    if (n == 0) { close_conn(sock); return -1; }       // peer closed
    if (n < 0) {
        if (errno == EINTR) continue;
        return -1;                                     // real error
    }
    got += (size_t)n;
}

Wrap raw system calls in helpers that return a small set of outcomes, so the rest of the code deals with a simple contract.

Quick check

How is this different from CWE-393? In CWE-393 the function returns the wrong value. In CWE-394 the value is legitimate, but the caller did not expect it.

Why do partial reads matter for security? Code that assumes a full buffer may use stale or uninitialized bytes, which is the pattern in the read() case from the record.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.