Detailed description of the impact of this finding.
There are several issues that lead to out of bounds array access here and lead to integer overflow vulnerability in the settle function within the PerpetualAtlanticVault contract, they are:
On line 413 strikes.length is not initially set to a value and is used in a loop.
On line 328 optionIds.length is initially set to null or empty and is also used in a loop.
The function called setLpAllowance posses an out of bounds array access.
So, the value in the function does not fall within the max length of the array, it appears to be set to zero or nothing.
This means that the functions the variable arrays are in are vulnerable to attacks like integer overflow whichI will demonstrate below in an exploit.
Test result: ok. 1 passed; 0 failed; finished in 839.01ms
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
```sol
// https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L243-L250
function setLpAllowance(bool increase) external onlyRole(DEFAULT_ADMIN_ROLE) {
increase
? collateralToken.approve(
addresses.perpetualAtlanticVaultLP,
type(uint256).max
)
: collateralToken.approve(addresses.perpetualAtlanticVaultLP, 0);
}
// https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L346-L351
// Transfer collateral token from perpetual vault to rdpx rdpxV2Core
collateralToken.safeTransferFrom(
addresses.perpetualAtlanticVaultLP,
addresses.rdpxV2Core,
ethAmount
);
// https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L429-L437
uint256 premium = calculatePremium(
strike,
amount,
timeToExpiry,
getUnderlyingPrice()
);
latestFundingPerStrike[strike] = latestFundingPaymentPointer;
fundingAmount += premium;
// https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L328
for (uint256 i = 0; i < optionIds.length; i++) {
// https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L413
for (uint256 i = 0; i < strikes.length; i++) {
Tools Used
VS Code.
Foundry.
Mythx.
Recommended Mitigation Steps
Adjust the code so that all values fall within the range.
Initialise the variable of optionsIds and strikes to a non zero value.
Lines of code
https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L243-L250 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L346-L351 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L429-L437 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L328 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L413 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L315-L369
Vulnerability details
Impact
Detailed description of the impact of this finding. There are several issues that lead to out of bounds array access here and lead to integer overflow vulnerability in the settle function within the PerpetualAtlanticVault contract, they are:
The function called setLpAllowance posses an out of bounds array access. So, the value in the function does not fall within the max length of the array, it appears to be set to zero or nothing.
This means that the functions the variable arrays are in are vulnerable to attacks like integer overflow whichI will demonstrate below in an exploit.
Proof of Concept
Exploits Foundry settle function integer overflow
Test Case
forge test -vvv --match-path "tests/perp-vault/Integration.t.sol" --match-test "testSettle"
Warning (2072): Unused local variable. --> tests/perp-vault/Integration.t.sol:378:5: | 378 | uint256 userBalance = vaultLp.balanceOf(address(1)); | ^^^^^^^^^^^^^^^^^^^
Running 1 test for tests/perp-vault/Integration.t.sol:Integration [PASS] testSettle() (gas: 612942) Traces: [612942] Integration::testSettle() ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000001) │ └─ ← () ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000001, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000002) │ └─ ← () ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000002, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000003, 0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [24681] MockToken::approve(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000003, spender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [34093] MockToken::mint(0x0000000000000000000000000000000000000001, 5000000000000000000 [5e18]) │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000001, amount: 5000000000000000000 [5e18]) │ └─ ← () ├─ [25293] MockToken::mint(0x0000000000000000000000000000000000000002, 20000000000000000000 [2e19]) │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000002, amount: 20000000000000000000 [2e19]) │ └─ ← () ├─ [25293] MockToken::mint(0x0000000000000000000000000000000000000003, 25000000000000000000 [2.5e19]) │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000003, amount: 25000000000000000000 [2.5e19]) │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000001, 0x0000000000000000000000000000000000000001) │ └─ ← () ├─ [143192] PerpetualAtlanticVaultLP::deposit(5000000000000000000 [5e18], 0x0000000000000000000000000000000000000001) │ ├─ [7644] PerpetualAtlanticVault::getUnderlyingPrice() [staticcall] │ │ ├─ [2324] MockRdpxEthPriceOracle::getRdpxPriceInEth() [staticcall] │ │ │ └─ ← 20000000 [2e7] │ │ └─ ← 20000000 [2e7] │ ├─ [51946] PerpetualAtlanticVault::updateFunding() │ │ ├─ [7315] MockToken::transfer(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 0) │ │ │ ├─ emit Transfer(from: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0) │ │ │ └─ ← true │ │ ├─ [4031] PerpetualAtlanticVaultLP::addProceeds(0) │ │ │ ├─ [583] MockToken::balanceOf(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c]) [staticcall] │ │ │ │ └─ ← 0 │ │ │ └─ ← () │ │ ├─ emit FundingPaid(sender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0, latestFundingPaymentPointer: 0) │ │ └─ ← () │ ├─ [18961] MockToken::transferFrom(0x0000000000000000000000000000000000000001, PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 5000000000000000000 [5e18]) │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000001, to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 5000000000000000000 [5e18]) │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000001, amount: 5000000000000000000 [5e18]) │ ├─ emit Deposit(caller: 0x0000000000000000000000000000000000000001, owner: 0x0000000000000000000000000000000000000001, assets: 5000000000000000000 [5e18], shares: 5000000000000000000 [5e18]) │ └─ ← 0x0000000000000000000000000000000000000000000000004563918244f40000 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000002, 0x0000000000000000000000000000000000000002) │ └─ ← () ├─ [36826] PerpetualAtlanticVaultLP::deposit(20000000000000000000 [2e19], 0x0000000000000000000000000000000000000002) │ ├─ [1144] PerpetualAtlanticVault::getUnderlyingPrice() [staticcall] │ │ ├─ [324] MockRdpxEthPriceOracle::getRdpxPriceInEth() [staticcall] │ │ │ └─ ← 20000000 [2e7] │ │ └─ ← 20000000 [2e7] │ ├─ [11472] PerpetualAtlanticVault::updateFunding() │ │ ├─ [3315] MockToken::transfer(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 0) │ │ │ ├─ emit Transfer(from: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0) │ │ │ └─ ← true │ │ ├─ [2031] PerpetualAtlanticVaultLP::addProceeds(0) │ │ │ ├─ [583] MockToken::balanceOf(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c]) [staticcall] │ │ │ │ └─ ← 5000000000000000000 [5e18] │ │ │ └─ ← () │ │ ├─ emit FundingPaid(sender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0, latestFundingPaymentPointer: 0) │ │ └─ ← () │ ├─ [3041] MockToken::transferFrom(0x0000000000000000000000000000000000000002, PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 20000000000000000000 [2e19]) │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000002, to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 20000000000000000000 [2e19]) │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000002, amount: 20000000000000000000 [2e19]) │ ├─ emit Deposit(caller: 0x0000000000000000000000000000000000000002, owner: 0x0000000000000000000000000000000000000002, assets: 20000000000000000000 [2e19], shares: 20000000000000000000 [2e19]) │ └─ ← 0x000000000000000000000000000000000000000000000001158e460913d00000 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000003, 0x0000000000000000000000000000000000000003) │ └─ ← () ├─ [36826] PerpetualAtlanticVaultLP::deposit(25000000000000000000 [2.5e19], 0x0000000000000000000000000000000000000003) │ ├─ [1144] PerpetualAtlanticVault::getUnderlyingPrice() [staticcall] │ │ ├─ [324] MockRdpxEthPriceOracle::getRdpxPriceInEth() [staticcall] │ │ │ └─ ← 20000000 [2e7] │ │ └─ ← 20000000 [2e7] │ ├─ [11472] PerpetualAtlanticVault::updateFunding() │ │ ├─ [3315] MockToken::transfer(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 0) │ │ │ ├─ emit Transfer(from: PerpetualAtlanticVault: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0) │ │ │ └─ ← true │ │ ├─ [2031] PerpetualAtlanticVaultLP::addProceeds(0) │ │ │ ├─ [583] MockToken::balanceOf(PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c]) [staticcall] │ │ │ │ └─ ← 25000000000000000000 [2.5e19] │ │ │ └─ ← () │ │ ├─ emit FundingPaid(sender: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 0, latestFundingPaymentPointer: 0) │ │ └─ ← () │ ├─ [3041] MockToken::transferFrom(0x0000000000000000000000000000000000000003, PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 25000000000000000000 [2.5e19]) │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000003, to: PerpetualAtlanticVaultLP: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], amount: 25000000000000000000 [2.5e19]) │ │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001 │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000003, amount: 25000000000000000000 [2.5e19]) │ ├─ emit Deposit(caller: 0x0000000000000000000000000000000000000003, owner: 0x0000000000000000000000000000000000000003, assets: 25000000000000000000 [2.5e19], shares: 25000000000000000000 [2.5e19]) │ └─ ← 0x0000000000000000000000000000000000000000000000015af1d78b58c40000 ├─ [0] VM::stopPrank() │ └─ ← () ├─ [564] PerpetualAtlanticVaultLP::balanceOf(0x0000000000000000000000000000000000000001) [staticcall] │ └─ ← 5000000000000000000 [5e18] ├─ [0] VM::expectRevert(Arithmetic over/underflow) │ └─ ← () └─ ← "Arithmetic over/underflow"
Test result: ok. 1 passed; 0 failed; finished in 839.01ms
Tools Used
VS Code. Foundry. Mythx.
Recommended Mitigation Steps
Adjust the code so that all values fall within the range. Initialise the variable of optionsIds and strikes to a non zero value.
Assessed type
Under/Overflow