The worst error handler is the one that catches the problem and then shrugs. CWE-390 is the weakness where the product detects a specific error but takes no action to handle it. The check exists, the branch exists, and the branch is empty.
It affects every language. Empty catch blocks, ignored return codes and a bare pass after an exception are the usual shapes.
How it happens
A developer wraps a call in try and catch to stop a noisy stack trace, or checks a return value and leaves the failure branch for later.
try {
loadPolicy(path);
} catch (IOException e) {
// ignore
}
// code continues as if the policy loaded
try:
token = verify(request.headers["Authorization"])
except InvalidToken:
pass # error detected, nothing done
user = lookup(token) # token is unbound or stale here
The program carries on with data that was never loaded or never verified.
A real example from the record: CVE-2022-21820, where a GPU data center manager detects an error due to a malformed request but does not act on it, leading to memory corruption.
What an attacker gains
MITRE lists an integrity impact: an attacker could use an ignored error condition to put the system in an unexpected state, which can lead to unintended logic executing and other unintended behavior. The attacker does not need to break a check. They only need to cause the error that the code ignores.
How to find it
MITRE says automated static analysis (SAST) can find some instances of this weakness. Linters also flag empty catch blocks and unused return values. For testing, MITRE suggests exercising the product extensively to discover where errors or return values are not handled, using techniques such as ad hoc testing, equivalence partitioning, fault tolerance testing, mutation testing and fuzzing.
A simple search helps: grep for empty catch bodies, except: pass, and calls whose return value is discarded.
How to fix it
MITRE's recommended solution is to handle each exception properly, so that you can be sure of the state of your system at any given moment. When a function returns an error, do one of three things:
- Fix the problem and try again.
- Alert the user that an error happened and let the program continue.
- Alert the user, then close and clean up the program.
For security decisions, the safe default is to fail closed. If verification fails, deny the request.
try:
token = verify(request.headers["Authorization"])
except InvalidToken:
log.warning("invalid token")
abort(401)
Quick check
Is logging the error enough?
Not by itself. A log line is useful, but the code still has to decide what state the system is in next: retry, fail the request, or stop.