Gas

在現實中發送乙太重入攻擊 - 備份功能如何將消息回調到目前合約?

  • January 12, 2018

以下引自solidity docs

pragma solidity ^0.4.0;

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
   /// Mapping of ether shares of the contract.
   mapping(address => uint) shares;
   /// Withdraw your share.
   function withdraw() {
       if (msg.sender.send(shares[msg.sender]))
           shares[msg.sender] = 0;
   }
}

問題是:**乙太幣轉賬總是包括程式碼執行,所以接收方可能是一個回調撤回的合約。**這將讓它獲得多次退款,並基本上取回合約中的所有乙太幣。

為避免重入,您可以使用 Checks-Effects-Interactions 模式,如下所述:

pragma solidity ^0.4.11;

contract Fund {
   /// Mapping of ether shares of the contract.
   mapping(address => uint) shares;
   /// Withdraw your share.
   function withdraw() {
       var share = shares[msg.sender];
       shares[msg.sender] = 0;
       msg.sender.transfer(share);
   }
}

突出顯示的部分是我有疑問的地方:我確實理解理論上存在重入攻擊,但考慮到備份功能有 2300 氣體限制(連結)這一事實,這不足以使消息回調到目前合約中。那麼在現實中,重入攻擊是如何起作用的呢?有高手能舉個例子嗎?謝謝!

更新

send在 2016年9 月之前transfer,預設將所有可用的 gas 傳遞給 CALL op。這在此送出https://github.com/ethereum/solidity/commit/9ca7472089a9f4d8bfec20e9e55c4f7ed2fb502e的 Solidity 編譯器中已更改。

// 首先手動提供氣體津貼,因為我們可能會發送零乙太幣。

// 如果我們發送超過零的乙太幣,將被清零。

上面的註釋意味著如果你發送 0 乙太幣,編譯器會為 CALL 操作顯式地設置氣體為 2300。否則,編譯器會為 CALL 操作設置 0 gas,在這種情況下,EVM 將添加 2300 gas 津貼,如Subtleties頁面中所述。

將所有可用氣體添加到外部函式呼叫的程式碼在 ExpressionCompiler::appendExternalFunctionCall() 中:

if (_functionType.gasSet())
   m_context << dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos));
else if (m_context.experimentalFeatureActive(ExperimentalFeature::V050))
   // Send all gas (requires tangerine whistle EVM)
   m_context << Instruction::GAS;
else
{
   // send all gas except the amount needed to execute "SUB" and "CALL"
   // @todo this retains too much gas for now, needs to be fine-tuned.
   u256 gasNeededByCaller = eth::GasCosts::callGas + 10;
   if (_functionType.valueSet())
       gasNeededByCaller += eth::GasCosts::callValueTransferGas;
   if (!existenceChecked)
       gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know
   m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB;
}

該文件寫於 2016 年 6 月https://github.com/ethereum/solidity/commit/2df142c49618138ba7f38f32a76022caecc68abb>。這是修復它的拉取請求<https://github.com/ethereum/solidity/pull/3197


上一個答案

這是證明可以進行重入呼叫的事務跟踪:https ://rinkeby.etherscan.io/vmtrace?txhash=0x2e77009bda0fc9c07a01a4589d9b426382521c6e04b84008ffda637a4268824f

在步驟 183 上執行 CALL 操作,花費 700 gas:

步驟 PC 操作 Gas GasCost 深度

$$ 183 $$ 118 致電 1189 1182 3 $$ 184 $$ 0 推1 482 3 4

它只花費 700 gas 的原因是因為呼叫時沒有傳遞任何值。

正如乙太坊 wiki 中的Subtleties頁面所解釋的:

CALL 有一個多部分的 gas 成本:

  • 700基地
  • 如果值非零,則增加 9000
  • 如果目標帳戶尚不存在,則額外增加 25000(注意:零餘額和不存在之間存在差異!)

對應的合約程式碼:

pragma solidity ^0.4.8;

contract Fund {

   bool mutex = false;

   function withdraw() {
       if (!mutex) {
           mutex = true;
           msg.sender.send(1);
           mutex = false;
       }
   }

   // deposit some funds for testing
   function deposit() payable {}
}

contract Attacker {

   Fund f;

   function Attacker(address fund) payable {
       f = Fund(fund);
   }

   function attack() {
       f.withdraw();
   }

   function () payable {
       f.withdraw();
   }
}

請注意,雖然 Solidity 文件中的原始合約容易受到重入攻擊,但它不能被利用來多次發送價值,因為第二次嵌套send呼叫會耗盡 2300 氣體津貼。這就是為什麼在我的範例中我必須使用互斥鎖。

另請注意,在 DAO hackcall方法中使用了將所有可用氣體傳遞給嵌套呼叫的方法,這與send預設情況下僅傳遞 2300 津貼不同。

引用自:https://ethereum.stackexchange.com/questions/30371