Summary
Unlike other functions that have array parameters and perform length checking, the permitDeposit
function does not perform array length checking.
And enrootDeposits
function also does not perform array length checking
Vulnerability Details
function permitDeposits(
address owner,
address spender,
address[] calldata tokens,
uint256[] calldata values,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external payable fundsSafu noNetFlow noSupplyChange nonReentrant {
LibSiloPermit.permits(owner, spender, tokens, values, deadline, v, r, s);
for (uint256 i; i < tokens.length; ++i) {
LibSiloPermit._approveDeposit(owner, spender, tokens[i], values[i]);
}
}
function permits(
address owner,
address spender,
address[] memory tokens,
uint256[] memory values,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
require(block.timestamp <= deadline, "Silo: permit expired deadline");
bytes32 structHash = keccak256(
abi.encode(
DEPOSITS_PERMIT_TYPEHASH,
owner,
spender,
keccak256(abi.encodePacked(tokens)),
keccak256(abi.encodePacked(values)),
_useNonce(owner),
deadline
)
);
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "Silo: permit invalid signature");
}
function _approveDeposit(
address account,
address spender,
address token,
uint256 amount
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
s.accts[account].depositAllowances[spender][token] = amount;
emit DepositApproval(account, spender, token, amount);
}
Look at these functions, there is no check that tokens.length
and values.length
must be equal.
As a result, values.length
can be larger than tokens.length
POC
it("difference length success but unexpected", async function () {
const nonce = await beanstalk.connect(user).depositPermitNonces(user.address);
const signature = await signSiloDepositTokensPermit(
user,
user.address,
user2.address,
[siloToken.address, siloToken2.address],
["1000", "1000", "500"],
nonce
);
this.result = await beanstalk
.connect(user)
.permitDeposits(
signature.owner,
signature.spender,
signature.tokens,
signature.values,
signature.deadline,
signature.split.v,
signature.split.r,
signature.split.s
);
const allowanceSiloToken = await beanstalk
.connect(user).depositAllowance(user.address, user2.address, siloToken.address);
const allowanceSiloToken2 = await beanstalk
.connect(user).depositAllowance(user.address, user2.address, siloToken2.address);
console.log('allowance silo token:', allowanceSiloToken);
console.log('allowance silo token2:', allowanceSiloToken2);
});
Impact
no funds are lost, but the impact is unexpected of purpose function.
Tools Used
Manual
Recommendations
require(tokens.length == values.length, "unexpected length")