Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lack of approval on withdrawal requests can be abused #224

Closed
code423n4 opened this issue Jun 8, 2023 · 3 comments
Closed

Lack of approval on withdrawal requests can be abused #224

code423n4 opened this issue Jun 8, 2023 · 3 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-140 unsatisfactory does not satisfy C4 submission criteria; not eligible for awards

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-06-stader/blob/main/contracts/UserWithdrawalManager.sol#L101-L103

Vulnerability details

Impact

Any user can submit withdrawal requests on behalf of other users, but there isn't an approval system. This can be abused to DoS specific users, as there are multiple checks on how many request a user can make.

Proof of Concept

Any user can submit a withdrawal request on behalf of other users, this is the function used to make new requests:

	function requestWithdraw(uint256 _ethXAmount, address _owner) external override whenNotPaused returns (uint256) {
	    if (_owner == address(0)) revert ZeroAddressReceived();
	    uint256 assets = IStaderStakePoolManager(staderConfig.getStakePoolManager()).previewWithdraw(_ethXAmount);
	    if (assets < staderConfig.getMinWithdrawAmount() || assets > staderConfig.getMaxWithdrawAmount()) {
	        revert InvalidWithdrawAmount();
	    }
->	    if (requestIdsByUserAddress[_owner].length + 1 > maxNonRedeemedUserRequestCount) {
	        revert MaxLimitOnWithdrawRequestCountReached();
	    }
	    IERC20Upgradeable(staderConfig.getETHxToken()).safeTransferFrom(msg.sender, (address(this)), _ethXAmount);
	    ethRequestedForWithdraw += assets;
	    userWithdrawRequests[nextRequestId] = UserWithdrawInfo(payable(_owner), _ethXAmount, assets, 0, block.number);
->	    requestIdsByUserAddress[_owner].push(nextRequestId);
	    emit WithdrawRequestReceived(msg.sender, _owner, nextRequestId, _ethXAmount, assets);
	    nextRequestId++;
	    return nextRequestId - 1;
	}

There is a hard limit on how many requests a user can make:

	if (requestIdsByUserAddress[_owner].length + 1 > maxNonRedeemedUserRequestCount) {
	    revert MaxLimitOnWithdrawRequestCountReached();
	}

Bob may decide to "donate" their funds by making maxNonRedeemedUserRequestCount small withdrawal requests to Alice: as a consequence, Alice can't make new withdrawal requests to herself, as her transactions would exceed the max.

Alice is effectively under DoS until Bob decides to stop making new requests, as Alice's requests can be easily front-run.

Tools Used

Manual review

Recommended Mitigation Steps

Consider modifying the function so that requests are under the requester address:

	function requestWithdraw(uint256 _ethXAmount, address _owner) external override whenNotPaused returns (uint256) {
	    if (_owner == address(0)) revert ZeroAddressReceived();
	    uint256 assets = IStaderStakePoolManager(staderConfig.getStakePoolManager()).previewWithdraw(_ethXAmount);
	    if (assets < staderConfig.getMinWithdrawAmount() || assets > staderConfig.getMaxWithdrawAmount()) {
	        revert InvalidWithdrawAmount();
	    }
+	    if (requestIdsByUserAddress[msg.sender].length + 1 > maxNonRedeemedUserRequestCount) {
-	    if (requestIdsByUserAddress[_owner].length + 1 > maxNonRedeemedUserRequestCount) {
	        revert MaxLimitOnWithdrawRequestCountReached();
	    }
	    IERC20Upgradeable(staderConfig.getETHxToken()).safeTransferFrom(msg.sender, (address(this)), _ethXAmount);
	    ethRequestedForWithdraw += assets;
	    userWithdrawRequests[nextRequestId] = UserWithdrawInfo(payable(_owner), _ethXAmount, assets, 0, block.number);
+	    requestIdsByUserAddress[msg.sender].push(nextRequestId);
-	    requestIdsByUserAddress[_owner].push(nextRequestId);
	    emit WithdrawRequestReceived(msg.sender, _owner, nextRequestId, _ethXAmount, assets);
	    nextRequestId++;
	    return nextRequestId - 1;
	}

Assessed type

Access Control

@code423n4 code423n4 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Jun 8, 2023
code423n4 added a commit that referenced this issue Jun 8, 2023
@c4-judge
Copy link
Contributor

Picodes marked the issue as duplicate of #221

@c4-judge
Copy link
Contributor

c4-judge commented Jul 2, 2023

Picodes marked the issue as satisfactory

@c4-judge c4-judge added satisfactory satisfies C4 submission criteria; eligible for awards duplicate-140 unsatisfactory does not satisfy C4 submission criteria; not eligible for awards and removed duplicate-221 satisfactory satisfies C4 submission criteria; eligible for awards labels Jul 2, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Jul 5, 2023

Picodes marked the issue as unsatisfactory:
Invalid

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-140 unsatisfactory does not satisfy C4 submission criteria; not eligible for awards
Projects
None yet
Development

No branches or pull requests

2 participants