CWE-584 is a small mistake with a quiet effect. A return statement inside a finally block causes any exception thrown in the try block to be discarded.
The caller never learns that something failed. An error that should have stopped a transaction, blocked a login or rejected a bad file simply vanishes, and the method returns as if all went well.
MITRE lists it as Not Language-Specific. It appears wherever a language has finally: Java, JavaScript, C#, Python and others.
How it happens
A finally block runs whether or not the try block throws. Its purpose is cleanup: closing files, releasing locks, rolling back. If you put a return there, the method completes normally from the finally, and the pending exception is thrown away.
MITRE's example shows the pattern, where an IllegalArgumentException will never be delivered to the caller because the finally block returns.
A concrete Java version:
// Vulnerable: the exception from validate() is lost
boolean isAllowed(Request r) {
boolean ok = false;
try {
validate(r); // throws SecurityException on failure
ok = true;
} finally {
return ok; // hides the exception
}
}
In this version ok stays false when validation throws, so the result is safe by luck. Change the default, or log and then return true in finally, and a failed check becomes a pass:
// Worse: default flips the meaning
boolean isAllowed(Request r) {
boolean ok = true; // optimistic default
try {
validate(r);
} finally {
return ok; // SecurityException is swallowed, returns true
}
}
Other variants include break or continue inside finally in a loop, which also cancel the in flight exception. JavaScript and Python both allow a return in finally to override a thrown error as well, so the lesson carries across languages.
What an attacker gains
MITRE lists the consequence as altered execution logic. What that means depends on the code around it.
- If the swallowed exception came from an authorization or validation step, the attacker passes a check they should fail.
- If it came from a failed write, the system may report success for data that was never stored.
- If it came from resource exhaustion handling, errors go unreported and problems stay hidden until they are large.
Even without a direct exploit, swallowed exceptions make incidents hard to diagnose. The logs show nothing because nothing was thrown.
How to find it
MITRE says automated static analysis can find some instances. This one is a good candidate for tooling since the pattern is syntactic.
- Compiler warnings. javac with
-Xlint:finallywarns when afinallyblock cannot complete normally. Turn it on. - Linters. Most ecosystems have a rule against control flow statements in
finally. ESLint hasno-unsafe-finally, for example. - Code search. Grep for
finallyblocks and look forreturn,break,continueandthrowinside. - Tests for failure paths. A test that forces the
trybody to throw and asserts the caller sees the exception will catch it.
How to fix it
MITRE's mitigation is direct: do not use a return statement inside the finally block. The finally block should have cleanup code.
- Move the return out of
finally. Return after thetrystatement or inside thetrybody. - Keep
finallyfor cleanup only: close, release, restore. - Let exceptions propagate, or catch them deliberately in a
catchblock where you decide what to do. - Enable the lint rule so the pattern cannot come back.
// Fixed: finally only cleans up, exception propagates
boolean isAllowed(Request r) {
try {
validate(r); // SecurityException reaches the caller
return true;
} finally {
releaseResources(); // cleanup only
}
}
If you need to translate an exception, do it in a catch block and throw the new one deliberately.
Error handling flaws can also live in third party code. A quick look at what you depend on is useful context. The free scan at gold.safeguard.sh/scan works in your browser with no login, keeps the file on your machine, and covers up to 100 dependencies.
Quick check
What happens to an exception when finally returns?
It is discarded. The method completes normally and the caller never sees the error.
What belongs in a finally block?
Cleanup code only, such as closing files and releasing locks.
How can I catch this automatically?
Enable compiler or linter rules, for example javac with the finally lint option or ESLint's no-unsafe-finally.