# The call to MsgValueSimulator with non zero msg.value will call to sender itself which will bypass the onlySelf check

Calling zkSync's MsgValueSimulator with nonzero msg.value and itself as recipient reenters the sender with corrupted register-derived value and calldata, enabling self-calls that bypass onlySelf.

- Payout: Contest reward
- Severity: high
- Program: zkSync
- Credited: ronnyx2017
- Disclosed: 2023-06-14
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2023-03-zksync-findings/issues/153
- Library page: https://bugbountylibrary.com/r/c4%3A2023-03-zksync%3AH-01
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:reentrancy

**Target:** MsgValueSimulator system contract (0x8009) / EfficientCall mimicCall register handling
**Root cause:** MsgValueSimulator forwards via EfficientCall.mimicCall, which repurposes r3-r5 registers; when the recipient is MsgValueSimulator itself, _getAbiParams reads corrupted registers, treating a fatPointer as msg.value and the original sender as the destination.
**Steps:**
1. Deploy contract calling `EfficientCall.rawCall(gasleft(), 0x8009, msg.value/2, msg.data[0:0])` with msg.value >= 2
2. MsgValueSimulator fallback reenters itself via mimicCall, corrupting r3-r5
3. Second fallback reads r3 as value (rawFatPointer) and r4 as to (original sender)
4. Sender contract receives a self-call with attacker-controlled calldata and huge value
5. Observe L2EthToken Transfer events: funds to 0x8009 stuck, and self-transfer of rawFatPointer value
**How found:** Read MsgValueSimulator fallback and EfficientCall.mimicCall, noticed mimicCall uses r1-r4 for standard ABI and r5 for who_to_mimic, so a self-targeted call makes _getAbiParams read garbage registers; built an integration test against zksync-era to observe register-derived values in Transfer logs.
**Exploitation:** Attacker makes a contract/account call MsgValueSimulator with nonzero value and itself as recipient; the simulator reenters the sender, which then calls itself with the original calldata, bypassing onlySelf-style msg.sender checks or breaking accounting that keys on msg.sender. With calldata length 0, msg.value of the self-call is controllable via fatPointer fields; nonzero length forces value >= 2^96 wei, making it impractical.
**Escalation:** Judge noted potential escalation to High for integrators: bridge contracts calling self, try/catch self-calls, vaults minting empty shares on no-op self-transfers; not demonstrated in-scope.
**Insight:** When a system contract forwards calls via a low-level opcode that reuses registers for ABI args, test the case where the recipient is the forwarder itself: the second pass reads stale/misinterpreted registers, turning a fatPointer into msg.value and the caller into the destination.
