Full Report
Solidity has error handling like most languages do. It looks similar to JavaScript with try and catch blocks. The docs can be read at here. In the initial example, the author gives a fairly simple code for the try with 3 options for the catch. First, catch Error(...) catches a revert from the external contract with a particular reason string. Second, Panic catches a serious error like division by zero, an integer underflow or assert. Finally, there is catch (bytes memory) that will catch all other unhandled errors. So, what if we could cause a denial of service by triggering one of these outcomes? Or, if we could bypass the checks implemented in a specific catch but not another? Let's hack things! The first option that the author considers is a wrong data type. Naturally, the compiler makes an assumption that the proper data type is returned. When the data returns uint256, it will use the address or any other data that is provided. The next case the user considers is returning data from a different location, such as dynamic arrays and strings. In the case of the string, the return value is 32. Although the author doesn't say this directly, I'm guessing this is the location of the dynamic byte array in the return data. What if no data is returned at all? The EVM returns an error that there is an odd-length and the argument is invalid. Since there is no data being sent back, this triggers the general catch case. What else can hit this catch? An empty fallback or executing code at an address with no code. Overall, an interesting article on the intricacies of try/catch statements in Solidity. If errors are not handled properly, it could lead to security issues.
Analysis Summary
# Vulnerability: Solidity Try/Catch Return Data Decoding Exception
## CVE Details
- **CVE ID**: N/A (Language Design Behavior / Logic Flaw)
- **CVSS Score**: N/A (Variable depending on implementation context)
- **CWE**: CWE-755: Improper Handling of Exceptional Conditions / CWE-20: Improper Input Validation
## Affected Systems
- **Products**: Solidity Compiler (solc)
- **Versions**: All versions supporting `try/catch` (v0.6.0 through current v0.8.x)
- **Configurations**: Smart contracts utilizing `try/catch` blocks for external calls where the `returns` clause expects specific data types or sizes.
## Vulnerability Description
In Solidity, the `try/catch` statement is designed to handle failures of external calls. However, a critical edge case exists: if the external call successfully returns data, but that data cannot be decoded into the types specified in the `returns` clause, an exception is thrown in the **calling contract**.
Because this decoding error occurs within the context of the caller's execution (after the external call has technically "succeeded" at the EVM level), it is not captured by the `catch` blocks. This results in the entire transaction reverting, despite the developer's intent to handle all possible failures.
Specific triggers identified:
1. **Type Mismatch**: Returning a data type different from the one expected (e.g., returning a `uint256` when an `address` is expected, though and addresses are often interchangeable, larger mismatches or complex types cause issues).
2. **Malformed Dynamic Data**: Returning a pointer to a dynamic array or string that exceeds the actual length of the returned data.
3. **Missing Return Data**: Executing a call against a contract that returns no data (or an address with no code/empty fallback) when the `try` block expects return values.
## Exploitation
- **Status**: PoC available (documented by The Red Guild).
- **Complexity**: Medium
- **Attack Vector**: Network (External Contract Interaction)
An attacker can provide a malicious contract as a "DataFeed" or "Paymaster" that returns deliberately malformed data. By triggering a decoding error, the attacker forces the calling contract to revert entirely, potentially leading to a Denial of Service (DoS) in logic that assumes the `catch` block will prevent failure (e.g., in ERC-4337 EntryPoint or yield aggregators).
## Impact
- **Confidentiality**: None
- **Integrity**: Low (Can disrupt state transitions)
- **Availability**: High (Primary impact: Denial of Service by forcing unhandled reverts)
## Remediation
### Patches
- This is a known behavior of the Solidity language. No patch is available as it is considered a feature of the decoding process (see Solidity Docs v0.8.20).
### Workarounds
- **Low-level Calls**: Use `address.call()` instead of `try/catch` for high-risk external interactions. This returns a boolean success flag and the raw `bytes` data, allowing the developer to manually validate data length and content before attempting to decode.
- **Validation**: If using `try/catch`, ensure the contract being called is trusted or that the failure of the calling contract (the unhandled revert) does not create a DoS condition for other users.
- **Check Codesize**: Before calling, verify that the target address contains code via `extcodesize`.
## Detection
- **Indicators of Compromise**: Transactions reverting with an "Error(string)" or "Panic" that should have been caught, or reverts with no message specifically during decoding stages.
- **Detection methods and tools**:
- Static Analysis: Slither or Slitherin (custom detectors for try/catch decoding).
- Manual Review: Audit all `try...returns` blocks where the target address is user-supplied.
## References
- [Solidity Documentation: Control Structures](https://docs.soliditylang.org/en/v0.8.20/control-structures.html#try-catch)
- [The Red Guild Blog: Catch me if you can!](https://blog.theredguild.org/catch-me-if-you-can/)
- [Solidity by Example: Try/Catch](https://solidity-by-example.org/try-catch/)