# Missing value validation in cross message handler allows unbacked fund transfer

Recall gateway register() double-counts msg.value across genesisCircSupply and collateral locks, letting a subnet register with unbacked supply and later drain FIL via release and kill.

- Payout: Contest reward
- Severity: medium
- Program: Recall
- Credited: 0xastronatey
- Disclosed: 2026-03-13
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2025-02-recall#m-03
- Library page: https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AM-03
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** GatewayManagerFacet.register / AssetHelper.lock (Solidity gateway contract)
**Root cause:** register() performs two independent lock() calls for genesisCircSupply and collateral, each checking msg.value >= value separately, so the same msg.value backs both without an aggregate sum check.
**Steps:**
1. Deploy a malicious subnet actor returning Native asset from supplySource() and collateralSource().
2. Call `gatewayDiamond.manager().register{value: 10 ether}(10 ether, 10 ether)` from the subnet actor.
3. Both lock() calls pass since msg.value >= 10 ether each; subnet records circSupply=10 and stake=10 with only 10 FIL deposited.
4. Commit parent finality at height 2 via SYSTEM_ACTOR.
5. Submit a BottomUpCheckpoint with a release message of 10 ether to attacker via `commitCheckpoint`.
6. Call `kill()` to reclaim the 10 ether stake, draining an extra 10 FIL from the gateway.
**How found:** Code review of register() spotted two separate lock() calls each validating msg.value independently, breaking the invariant that locked funds equal subnet supply plus collateral.
**Exploitation:** Attacker registers a subnet with genesisCircSupply == collateral == X while sending only X FIL, then issues a bottom-up release of X to themselves and kills the subnet to reclaim the X stake, netting X FIL from the gateway's pooled funds.
**Escalation:** Repeat across many subnets or scale X to drain the gateway's entire FIL balance backing other subnets.
**Insight:** When a function performs multiple deposits against a single msg.value, check whether each internal lock/transfer re-validates the same balance independently — aggregate accounting invariants (sum of credits == msg.value) are the real check, not per-call checks.
