A PriceFeed is able to both approve and disapprove usd price.
Proof of Concept
Lets say that Role.PriceFeed_1 proposes a new price for given contracts.
Role.PriceFeed_2 calls disapproveUSDPrice and his usdUpdateProposal.disapprovals[msg.sender] = _usdProposalId; are set.
Due to a missing check in the approveUSDPrice function, Role.PriceFeed_2 can now also call disapproveUSDPrice.
This is not intended because in the disapprove function there are checks implemented for checking if the PriceFeed has already approved or disapproved. How ever the approve function only checks if the PriceFeed has already approved(it doesn't check if he's already disapproved). Which means that the PriceFeed roles can first disapprove and afterwards approve.
function approveUSDPrice(
uint256 _price
)
external
onlyOneOfRoles(
[
Role.PriceFeed_1,
Role.PriceFeed_2,
Role.PriceFeed_3,
Role.PriceFeed_4,
Role.PriceFeed_5
]
)
{
if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
if (usdUpdateProposal.proposer == msg.sender)
revert ProposerCannotApproveError();
if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
revert ProposalAlreadyApprovedError();
if (usdUpdateProposal.proposedPrice != _price)
revert ProposalPriceNotMatchedError();
usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
usdUpdateProposal.approvalsCount++;
if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) {
_execUSDPriceUpdate();
}
emit ApprovedUSDPrice(msg.sender);
}
function disapproveUSDPrice(
uint256 _price
)
external
onlyOneOfRoles(
[
Role.PriceFeed_1,
Role.PriceFeed_2,
Role.PriceFeed_3,
Role.PriceFeed_4,
Role.PriceFeed_5
]
)
{
if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
revert ProposalAlreadyApprovedError();
if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
revert ProposalAlreadyDisapprovedError();
if (usdUpdateProposal.proposedPrice != _price)
revert ProposalPriceNotMatchedError();
usdUpdateProposal.disapprovalsCount++;
usdUpdateProposal.disapprovals[msg.sender] = _usdProposalId;
emit DisapprovedUSDPrice(msg.sender);
if (usdUpdateProposal.disapprovalsCount >= DISAPPROVE_THRESHOLD) {
delete usdUpdateProposal;
emit RemovedUSDProposal();
}
}
Tools Used
Manual review
Recommended Mitigation Steps
Add this check if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError(); to the approveUSDPrice function.
Lines of code
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L177-L242
Vulnerability details
Impact
A PriceFeed is able to both approve and disapprove usd price.
Proof of Concept
disapproveUSDPrice
and hisusdUpdateProposal.disapprovals[msg.sender] = _usdProposalId;
are set.approveUSDPrice
function, Role.PriceFeed_2 can now also calldisapproveUSDPrice
.This is not intended because in the
disapprove function
there are checks implemented for checking if the PriceFeed has already approved or disapproved. How ever theapprove function
only checks if the PriceFeed has already approved(it doesn't check if he's already disapproved). Which means that the PriceFeed roles can first disapprove and afterwards approve.Tools Used
Manual review
Recommended Mitigation Steps
Add this check
if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError();
to theapproveUSDPrice
function.Assessed type
Access Control