function arbitraryCall(address who, bytes memory data) public lock externallyGoverned {
// cannot have an active incentive for the callee
require(incentives[who] == 0, "inc");
...
When an incentiveToken is claimed after endStream, incentives[who] will be 0 for that incentiveToken.
If the protocol gov is malicious or compromised, they can call arbitraryCall() with the address of the incentiveToken as who and transferFrom() as calldata and steal all the incentiveToken in the victim's wallet balance up to the allowance amount.
PoC
Alice approved USDC to the streaming contract;
Alice called createIncentive() and added 1,000 USDC of incentive;
After the stream is done, the stream creator called claimIncentive() and claimed 1,000 USDC;
The compromised protocol gov can call arbitraryCall() and steal all the USDC in Alice's wallet balance.
Recommendation
Consider adding a mapping: isIncentiveToken, setting isIncentiveToken[incentiveToken] = true in createIncentive(), and require(!isIncentiveToken[who], ...) in arbitraryCall().
Handle
WatchPug
Vulnerability details
https://github.com/code-423n4/2021-11-streaming/blob/56d81204a00fc949d29ddd277169690318b36821/Streaming/src/Locke.sol#L733-L735
When an incentiveToken is claimed after
endStream
,incentives[who]
will be0
for thatincentiveToken
.If the protocol gov is malicious or compromised, they can call
arbitraryCall()
with the address of the incentiveToken aswho
andtransferFrom()
as calldata and steal all the incentiveToken in the victim's wallet balance up to the allowance amount.PoC
USDC
to the streaming contract;createIncentive()
and added1,000 USDC
of incentive;claimIncentive()
and claimed1,000 USDC
;The compromised protocol gov can call
arbitraryCall()
and steal all the USDC in Alice's wallet balance.Recommendation
Consider adding a mapping:
isIncentiveToken
, settingisIncentiveToken[incentiveToken] = true
increateIncentive()
, andrequire(!isIncentiveToken[who], ...)
inarbitraryCall()
.