Summary
initWithdraw in Gateway.sol does not verify that msg.sender is a registered or staked operator. Any address holding pegBTC can call this function to lock a legitimate user's pegin deposit, preventing the rightful operator from processing the withdrawal.
Affected Component
- File:
src/Gateway.sol
- Function:
initWithdraw (line ~436)
- Contract:
GatewayUpgradeable
Vulnerability Details
The initWithdraw function has no access control beyond requiring the caller to hold and approve sufficient pegBTC:
function initWithdraw(bytes16 instanceId, bytes16 graphId) external {
// ... status checks ...
peginData.status = PeginStatus.Locked;
_safeTransferFrom(pegBTC, msg.sender, address(this), lockAmount);
withdrawData.operatorAddress = msg.sender;
withdrawData.status = WithdrawStatus.Initialized;
// ...
}
There is no check that:
msg.sender is a registered operator via stakeManagement
msg.sender has sufficient locked stake (unlike postGraphData which checks lockedStakeOf >= minStakeAmount)
msg.sender corresponds to the graphData.operatorPubkey for this graph
Compare with postGraphData (line 401-406) which properly validates operator registration and stake:
address operatorStakeAddress = stakeManagement.pubkeyToAddress(graphData.operatorPubkey);
if (operatorStakeAddress == address(0)) revert OperatorNotRegistered();
if (stakeManagement.lockedStakeOf(operatorStakeAddress) < minStakeAmount) revert StakeInsufficient();
Steps to Reproduce
- Attacker obtains pegBTC (via DEX, bridge deposit, or any means)
- Attacker approves Gateway to spend their pegBTC
- Attacker identifies a pegin in
Withdrawable status with a valid posted graph
- Attacker calls
initWithdraw(instanceId, graphId) before the legitimate operator
- The pegin status changes to
Locked, with attacker as operatorAddress
Impact
- Griefing/DoS on bridge withdrawals: Attacker can front-run legitimate operators and lock pegin deposits. Since
proceedWithdraw requires the Bitcoin kickoff transaction to match graphData.kickoffTxid (which only the real operator can produce), the withdrawal becomes stuck.
- Recovery requires committee intervention: Only
committeeCancelWithdraw (requiring committee signatures) can unlock the pegin, adding significant delay and operational burden.
- Cost to attacker: Only needs to hold pegBTC temporarily (returned on cancel). Effective griefing at minimal cost.
- Repeated attacks: Attacker can repeatedly lock the same pegin after each committee cancel.
Suggested Remediation
Add operator validation to initWithdraw:
function initWithdraw(bytes16 instanceId, bytes16 graphId) external {
GraphData storage graphData = graphDataMap[graphId];
address operatorStakeAddress = stakeManagement.pubkeyToAddress(graphData.operatorPubkey);
require(msg.sender == operatorStakeAddress, "Not the graph operator");
// ... rest of function
}
Severity
Medium - Denial of service on bridge withdrawal flow. No direct fund loss, but requires committee intervention to resolve and can be repeated indefinitely.
Summary
initWithdrawinGateway.soldoes not verify thatmsg.senderis a registered or staked operator. Any address holding pegBTC can call this function to lock a legitimate user's pegin deposit, preventing the rightful operator from processing the withdrawal.Affected Component
src/Gateway.solinitWithdraw(line ~436)GatewayUpgradeableVulnerability Details
The
initWithdrawfunction has no access control beyond requiring the caller to hold and approve sufficient pegBTC:There is no check that:
msg.senderis a registered operator viastakeManagementmsg.senderhas sufficient locked stake (unlikepostGraphDatawhich checkslockedStakeOf >= minStakeAmount)msg.sendercorresponds to thegraphData.operatorPubkeyfor this graphCompare with
postGraphData(line 401-406) which properly validates operator registration and stake:Steps to Reproduce
Withdrawablestatus with a valid posted graphinitWithdraw(instanceId, graphId)before the legitimate operatorLocked, with attacker asoperatorAddressImpact
proceedWithdrawrequires the Bitcoin kickoff transaction to matchgraphData.kickoffTxid(which only the real operator can produce), the withdrawal becomes stuck.committeeCancelWithdraw(requiring committee signatures) can unlock the pegin, adding significant delay and operational burden.Suggested Remediation
Add operator validation to
initWithdraw:Severity
Medium - Denial of service on bridge withdrawal flow. No direct fund loss, but requires committee intervention to resolve and can be repeated indefinitely.