From d0343117ddfbfdc229815518caf0fc5df3384d36 Mon Sep 17 00:00:00 2001 From: 0xSandyy <0xSandyy@gmail.com> Date: Thu, 6 Jun 2024 23:08:34 +0545 Subject: [PATCH 1/5] msg.value in a loop vulnerabilitiy added --- vulnerabilities/msgvalue-loop.md | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 vulnerabilities/msgvalue-loop.md diff --git a/vulnerabilities/msgvalue-loop.md b/vulnerabilities/msgvalue-loop.md new file mode 100644 index 0000000..42837b6 --- /dev/null +++ b/vulnerabilities/msgvalue-loop.md @@ -0,0 +1,36 @@ +# Using ``msg.value`` in a loop + +The particularity with msg.value is that ``msg.value`` is set on every contract call and not on the transaction level. Thus, if the function with loop is transferring ``msg.value`` on every iteration, all the ``msg.value`` sent to the function is transferred in the first iteration and other iteration will proceed with ``msg.value = 0`` which may cause unexpected consequences. + +```solidity +contract depositer { + function deposit(address weth) payable external { + for (uint i = 0; i < 5; i ++) { + WETH(weth).deposit{value: msg.value}(); // all the ``msg.value`` will be used in the first iteration + } + } +} +``` + +Also, if a function has a check like ``msg.value > 0.1e18``, that function can be called multiple times in a same transaction as ``msg.value`` value is not subtracted unless it's transferred out of the contract. Thus, Using ``msg.value`` inside a loop is dangerous because this might allow the sender to “re-use” the msg.value. + +```solidity +function batchBuy(address[] memory addr) external payable{ + mapping (uint => address) nft; + for (uint i = 0; i < addr.length; i++) { + buy1NFT(addr[i]) + } + function buy1NFT(address to) internal { + if (msg.value < 1 ether) { revert("not enough ether") } // buy unlimited times after paying 1 ether once + nft[numero] = address; + } +} +``` + +Reuse of ``msg.value`` can show up with payable multicalls. Multicalls enable a user to submit a list of transactions to avoid paying the 21,000 gas transaction fee over and over. However, ``msg.value`` gets ``re-used`` while looping through the functions to execute, potentially enabling the user to double spend. + +This was the root cause of the [Opyn Hack](https://peckshield.medium.com/opyn-hacks-root-cause-analysis-c65f3fe249db). + +## Sources +- https://www.rareskills.io/post/smart-contract-security#:~:text=Using%20msg.,show%20up%20with%20payable%20multicalls. +- https://trustchain.medium.com/ethereum-msg-value-reuse-vulnerability-5afd0aa2bcef \ No newline at end of file From 441118fc7c50600b69903d5d37829c098b92e492 Mon Sep 17 00:00:00 2001 From: 0xSandyy <0xSandyy@gmail.com> Date: Sun, 9 Jun 2024 16:56:32 +0545 Subject: [PATCH 2/5] Vulnerability Updated 06/09 --- vulnerabilities/msgvalue-loop.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/vulnerabilities/msgvalue-loop.md b/vulnerabilities/msgvalue-loop.md index 42837b6..85947bf 100644 --- a/vulnerabilities/msgvalue-loop.md +++ b/vulnerabilities/msgvalue-loop.md @@ -1,19 +1,22 @@ -# Using ``msg.value`` in a loop +# Using ``msg.value`` in a loop. -The particularity with msg.value is that ``msg.value`` is set on every contract call and not on the transaction level. Thus, if the function with loop is transferring ``msg.value`` on every iteration, all the ``msg.value`` sent to the function is transferred in the first iteration and other iteration will proceed with ``msg.value = 0`` which may cause unexpected consequences. +The value of ``msg.value`` in a transaction’s call never gets updated, even if the called contract ends up sending some or all of the ETH to another contract. This means that using ``msg.value`` in ``for`` or ``while`` loops, without extra accounting logic, will either lead to the transaction reverting (when there are no longer sufficient funds for later iterations), or to the contract being drained (when the contract itself has an ETH balance). ```solidity contract depositer { function deposit(address weth) payable external { for (uint i = 0; i < 5; i ++) { - WETH(weth).deposit{value: msg.value}(); // all the ``msg.value`` will be used in the first iteration + WETH(weth).deposit{value: msg.value}(); } } } ``` +In the above example, first iteration will use all the ``msg.value`` sent and other iterations can: +1. Drain the contract if some ETH balance exists inside the contract. +2. Revert on zero value transfer if ETH balance doesn't exist inside the contract. +3. Succeed with zero value transfer. -Also, if a function has a check like ``msg.value > 0.1e18``, that function can be called multiple times in a same transaction as ``msg.value`` value is not subtracted unless it's transferred out of the contract. Thus, Using ``msg.value`` inside a loop is dangerous because this might allow the sender to “re-use” the msg.value. - +Also, if a function has a check like ``msg.value > 0.1e18``, that function can be called multiple times in a same transaction as ``msg.value`` is not updated in a transaction call. ```solidity function batchBuy(address[] memory addr) external payable{ mapping (uint => address) nft; @@ -27,9 +30,9 @@ function batchBuy(address[] memory addr) external payable{ } ``` -Reuse of ``msg.value`` can show up with payable multicalls. Multicalls enable a user to submit a list of transactions to avoid paying the 21,000 gas transaction fee over and over. However, ``msg.value`` gets ``re-used`` while looping through the functions to execute, potentially enabling the user to double spend. + Thus, Using ``msg.value`` inside a loop is dangerous because this might allow the sender to ``re-use`` the ``msg.value``. -This was the root cause of the [Opyn Hack](https://peckshield.medium.com/opyn-hacks-root-cause-analysis-c65f3fe249db). +Reuse of ``msg.value`` can also show up with payable multicalls. Multicalls enable a user to submit a list of transactions to avoid paying the 21,000 gas transaction fee over and over. However, If ``msg.value`` gets ``re-used`` while looping through the functions to execute, it can cause a serious issue like the [Opyn Hack](https://peckshield.medium.com/opyn-hacks-root-cause-analysis-c65f3fe249db). ## Sources - https://www.rareskills.io/post/smart-contract-security#:~:text=Using%20msg.,show%20up%20with%20payable%20multicalls. From 6dee120faa74d8c9f8610ff7f0f90132a8ed9f27 Mon Sep 17 00:00:00 2001 From: 0xSandyy <0xSandyy@gmail.com> Date: Sun, 9 Jun 2024 17:07:17 +0545 Subject: [PATCH 3/5] Vulnerability Updated: Fixed indentation and other minor changes. --- vulnerabilities/msgvalue-loop.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vulnerabilities/msgvalue-loop.md b/vulnerabilities/msgvalue-loop.md index 85947bf..b7e1834 100644 --- a/vulnerabilities/msgvalue-loop.md +++ b/vulnerabilities/msgvalue-loop.md @@ -16,15 +16,20 @@ In the above example, first iteration will use all the ``msg.value`` sent and ot 2. Revert on zero value transfer if ETH balance doesn't exist inside the contract. 3. Succeed with zero value transfer. -Also, if a function has a check like ``msg.value > 0.1e18``, that function can be called multiple times in a same transaction as ``msg.value`` is not updated in a transaction call. +Also, if a function has a check like ``require(msg.value == 1e18, "Not Enough Balance")``, that function can be called multiple times in a same transaction by sending ``1 ether`` once as ``msg.value`` is not updated in a transaction call. + ```solidity function batchBuy(address[] memory addr) external payable{ mapping (uint => address) nft; + for (uint i = 0; i < addr.length; i++) { buy1NFT(addr[i]) } + function buy1NFT(address to) internal { - if (msg.value < 1 ether) { revert("not enough ether") } // buy unlimited times after paying 1 ether once + if (msg.value < 1 ether) { // buy unlimited times after sending 1 ether once + revert("Not enough ether"); + } nft[numero] = address; } } From db0995e038d8bde490ae85a60b2f3dad992f1235 Mon Sep 17 00:00:00 2001 From: 0xSandyy <0xSandyy@gmail.com> Date: Sun, 9 Jun 2024 17:24:30 +0545 Subject: [PATCH 4/5] Listing added for the vulnerability --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 20204af..28e5018 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,4 @@ - [Off-By-One](./vulnerabilities/off-by-one.md) - [Lack of Precision](./vulnerabilities/lack-of-precision.md) - [Unbounded Return Data](./vulnerabilities/unbounded-return-data.md) +- [Using ``msg.value`` in a loop](./vulnerabilities/msgvalue-loop.md) From e77623c1ea1fb6fe97f8c2d39b030e8c308887bb Mon Sep 17 00:00:00 2001 From: 0xSandyy <0xSandyy@gmail.com> Date: Mon, 10 Jun 2024 10:55:08 +0545 Subject: [PATCH 5/5] msgValueLoop vuln updated: added requested changes --- README.md | 2 +- vulnerabilities/msgvalue-loop.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 28e5018..b814488 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,4 @@ - [Off-By-One](./vulnerabilities/off-by-one.md) - [Lack of Precision](./vulnerabilities/lack-of-precision.md) - [Unbounded Return Data](./vulnerabilities/unbounded-return-data.md) -- [Using ``msg.value`` in a loop](./vulnerabilities/msgvalue-loop.md) +- [Using ``msg.value`` in a Loop](./vulnerabilities/msgvalue-loop.md) diff --git a/vulnerabilities/msgvalue-loop.md b/vulnerabilities/msgvalue-loop.md index b7e1834..55105b7 100644 --- a/vulnerabilities/msgvalue-loop.md +++ b/vulnerabilities/msgvalue-loop.md @@ -1,4 +1,4 @@ -# Using ``msg.value`` in a loop. +# Using ``msg.value`` in a Loop The value of ``msg.value`` in a transaction’s call never gets updated, even if the called contract ends up sending some or all of the ETH to another contract. This means that using ``msg.value`` in ``for`` or ``while`` loops, without extra accounting logic, will either lead to the transaction reverting (when there are no longer sufficient funds for later iterations), or to the contract being drained (when the contract itself has an ETH balance). @@ -11,10 +11,10 @@ contract depositer { } } ``` -In the above example, first iteration will use all the ``msg.value`` sent and other iterations can: -1. Drain the contract if some ETH balance exists inside the contract. -2. Revert on zero value transfer if ETH balance doesn't exist inside the contract. -3. Succeed with zero value transfer. +In the above example, first iteration will use all the ``msg.value`` for the external call and all other iterations can: +- Drain the contract if enough ETH balance exists inside the contract to cover all the iterations. +- Revert if enough ETH balance doesn't exist inside the contract to cover all the iterations. +- Succeed if the external implementation succeeds with zero value transfers. Also, if a function has a check like ``require(msg.value == 1e18, "Not Enough Balance")``, that function can be called multiple times in a same transaction by sending ``1 ether`` once as ``msg.value`` is not updated in a transaction call. @@ -35,7 +35,7 @@ function batchBuy(address[] memory addr) external payable{ } ``` - Thus, Using ``msg.value`` inside a loop is dangerous because this might allow the sender to ``re-use`` the ``msg.value``. +Thus, using ``msg.value`` inside a loop is dangerous because this might allow the sender to ``re-use`` the ``msg.value``. Reuse of ``msg.value`` can also show up with payable multicalls. Multicalls enable a user to submit a list of transactions to avoid paying the 21,000 gas transaction fee over and over. However, If ``msg.value`` gets ``re-used`` while looping through the functions to execute, it can cause a serious issue like the [Opyn Hack](https://peckshield.medium.com/opyn-hacks-root-cause-analysis-c65f3fe249db).