Full Report
The main Solidity code generator had a compiler bug in the intermediate representation (IR). This is the story and impact of the bug from versions 0.8.28 and 0.8.33. The IR pipeline generates reusable Yul helper functions during code generation. These helpers are deduplicated by name, so there can only be a single Yul function per name. The helper responsible for clearing a storage slot (delete) derives its name from the Solidity type being cleared. For instance, storage_set_to_zero_t_address for the address and the zeroth slot. This is where the bug lives: the name does not include the type of storage - persistent or transient. Since there can only be one, the clearing operation encountered by the compiler determines the implementation used. There are two cases for this: transient when expecting persistent and persistent when expecting transient. Notably, this writes data to the wrong slot! The article gives an example of the persistent being used when it should be transient. In this case, the owner address is in storage slot 0 and a locking address variable in transient slot 1. Upon setting the transient lock, the owner value is set unintentionally. The other example is the opposite type. The contract has a mapping of uint256 to address as persistent storage and an address for the caller in transient storage. This code has an approval, a revoked approval, and a run to demonstrate the issue. In this case, if you try to call revoke(), it simply will not work. Neat! The Solidity team's severity assessment seems fair. First, projects that run their test suite in --via-ir before deployment would have noticed this behaviour. This feels off at first, but I definitely don't trust the compiler, so I've tested in multiple settings like this before. Following the bug report, they found three affected contracts, which were notified and fixed. There's a separate blog post from the discoverers of the bug, Hexens, about this. While the Solidity team thinks this was low impact, Hexens believes it was critical. I think that assessing the impact of developer tools is hard. Is the impact the worst possible impact or the impact + likelihood? If you go off the first, it's really bad; if you go off the second, the overall impact isn't very bad. The transient keyword isn't too popular yet; this would have been more likely in the future. In the case of the Vyper reentrancy bug, which affected a wide range of contracts with very concrete security implications. Overall, a great blog post on the reason behind a compiler bug and the issues that resulted from it. I appreciated the concrete examples. The post doesn't mention how the bug is found which I would also like to know.
Analysis Summary
# Vulnerability: Solidity IR Compiler Storage Clearing Helper Name Collision
## CVE Details
- CVE ID: Not explicitly provided in the summary. (Likely assigned post-report, check official Solidity advisory for the number).
- CVSS Score: Not explicitly provided. Solidity team assigned an internal severity of **High**. Hexens assessed it as **Critical**.
- CWE: CWE-770 (Improper Neutralization of Special Elements Used in an Operation on a Resource Location) or similar logic error in code generation.
## Affected Systems
- Products: Solidity Compiler (solc)
- Versions: 0.8.28 through 0.8.33 (inclusive)
- Configurations: Only when compiled with the **IR pipeline** enabled (`--via-ir` flag or `settings.viaIR` in Standard JSON). Contracts compiled without `--via-ir` are unaffected.
## Vulnerability Description
The Solidity IR code generator generated reusable Yul helper functions for clearing storage slots (e.g., `delete`). These helpers are deduplicated by name. The function responsible for clearing a slot, `storageSetToZeroFunction`, derived its name solely from the *type* of data being cleared, *failing to include whether the storage location was **persistent** or **transient***.
Because persistent and transient clearing operations use distinct opcodes (`sstore` vs `tstore`), this naming collision causes the compiler to reuse a single helper implementation. When a contract uses `delete` on both a persistent variable and a transient variable of a matching value type (or a type that maps to the same slot-clearing logic, such as array clearing), the operation encountered first by the compiler dictates the implementation used for both, causing subsequent operations to execute the wrong storage write (e.g., using the persistent clear implementation on transient storage, or vice versa). This results in **writing data to the wrong storage slot**.
## Exploitation
- Status: Zero *known* deployed affected contracts were in use and fixed post-discovery. **PoC available** via the discoverer's analysis (Hexens) and the examples provided in the article (unintentional overwrites of owner/locking addresses).
- Complexity: The exploit requires specific conditions (IR compilation + use of `delete` on both persistent and transient storage with matching value types within the same compilation unit). If these conditions are met, the impact is immediate and deterministic.
- Attack Vector: The impact occurs during contract execution (runtime).
## Impact
- Confidentiality: Potential modification/leakage of data if overwritten sensitive persistent/transient data is subsequently read.
- Integrity: **High**. Unintentional writing/zeroing of state variables across the persistent/transient boundary, leading to unexpected contract behavior, privilege escalation, or denial of service.
- Availability: Potential impact depending on which state variables are overwritten (e.g., overwriting essential logic/owner variables).
## Remediation
### Patches
- Solidity version **0.8.34** fixes the bug.
### Workarounds
1. **Avoid the IR Pipeline:** Do not compile contracts with `--via-ir`. (This is the primary reason only a small number of contracts were affected).
2. **Avoid `delete` on Transient Storage:** If forced to use an older compiler version, ensure zero-value assignment (`_var = 0`) is used instead of `delete` on any transient state variable, provided the corresponding persistent delete operation exists.
3. **Review Inheritance:** If using `delete` on transient variables, ensure no base contract or inherited library/function causes a collision by performing `delete` on a persistent storage element of a matching type.
## Detection
- **Indicators of Compromise:** Look for unexpected state transitions following a `delete` operation on a transient variable, particularly if persistent storage variables (like owner addresses) change unexpectedly.
- **Detection Methods and Tools:** Review compilation builds to confirm `--via-ir` usage. Audit codebases for mixed usage of `delete` on transient variables alongside any operation that clears persistent storage (especially slot-level array clearing).
## References
- Vendor Advisory: Solidity Team Post on 2026-02-18
- Discoverer Analysis: hxxps://hexens.io/research/solidity-compiler-bug-tstore-poison
- Fix Release: Solidity 0.8.34 Release Announcement (mentioned proximity to this summary).