Safeguard
Application Security•

CWE-393 Return of Wrong Status Code: How It Breaks Security Logic

A function that reports the wrong result makes callers act on a falsehood. CWE-393 explained with real CVEs, including the Apple goto fail bug, and fixes.

Safeguard Security Team
4 min read

Every caller trusts the return value. If the value is wrong, the caller makes the wrong decision with full confidence. CWE-393 describes a function or operation that returns an incorrect value or status code that does not indicate the true result of execution, causing the product to modify its behavior based on the incorrect result.

It matters most when the function feeds a security decision. MITRE puts it plainly: the wrong status code can cause the product to assume that an action is safe or correct, even when it is not.

How it happens

There are a few common roots:

  • A code path that skips the real check but still reaches a success return.
  • A mapping from internal errors to public status codes that collapses distinct failures.
  • Platform specific behavior that returns something other than the documented value.

The record lists real cases:

  • CVE-2003-1132: a DNS server returns the wrong response code for a non-existent AAAA record, which effectively says the domain is inaccessible.
  • CVE-2001-1509: a hardware specific implementation of a system call causes incorrect results from geteuid.
  • CVE-2001-1559: a chain where a system call returns the wrong value, leading to a NULL dereference.
  • CVE-2014-1266: a chain in which an incorrect goto in Apple's SSL product bypasses certificate validation, allowing an adversary in the middle attack. This is the well known "goto fail" bug.

The goto fail case is the clearest teaching example. A stray jump made the validation function return success early, so the later signature check never ran. The function reported "verified" when it had not verified.

A smaller sketch of the same shape:

int verify_cert(cert_t *c) {
    int err;
    if ((err = check_hash(c)) != 0)
        goto fail;
        goto fail;                 // duplicated line always jumps
    if ((err = check_signature(c)) != 0)
        goto fail;
fail:
    return err;                    // err is 0 here, so "valid"
}

Because the second goto is not tied to the if, the signature check is unreachable, and err still holds the success value from the previous step.

What an attacker gains

MITRE lists an integrity impact: the system can end up in a state that leads to unexpected logic executing or other unintended behavior. When the function guards trust, the attacker gains whatever trust the check was supposed to deny. In the goto fail case, that meant a forged server certificate being accepted.

How to find it

MITRE names fuzzing as a detection method: generating large numbers of diverse inputs, randomly or algorithmically, and invoking the code with them, which can expose paths that return unexpected values. Other checks:

  • Write tests that assert both outcomes of every security check. A test that only feeds valid data would never notice a function that always says yes.
  • Turn on compiler warnings for unreachable code and misleading indentation. They would have flagged the goto fail pattern.
  • Use braces on every conditional body, and enable linters that enforce it.
  • Review any function whose return value gates a trust decision, and read the success path as skeptically as the failure path.

How to fix it

The record carries no formal mitigations, so these are plain engineering steps:

  1. Keep security checks small and single purpose, so every path to a success return is easy to audit.
  2. Initialize the result to failure and set success only after the last check passes.
  3. Cover each check with a negative test and, where you can, with fuzzing.
  4. Verify third party and platform behavior you depend on. If a system call can return an unexpected value on some hardware, wrap it and validate.
  5. Keep dependencies patched. Several of the cases above were bugs in widely used components that downstream products inherited.
int verify_cert(cert_t *c) {
    int err = -1;                          // default: invalid
    if (check_hash(c) != 0) goto out;
    if (check_signature(c) != 0) goto out;
    err = 0;                               // success only at the end
out:
    return err;
}

Quick check

Why default the result to failure? Then a skipped check or an unexpected jump produces a denial, not an accidental approval.

How does fuzzing help with wrong status codes? It drives the function with many unusual inputs, which can reach paths that return unexpected values and show up as crashes or surprising results.

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.