向回退函式轉發gas的意義及其與重入攻擊的關係
我正在閱讀https://solidity-by-example.org/fallback/以及<address>.send 與 <address>.transfer 最佳實踐用法中發送、轉移和呼叫之間的區別?.
根據我目前的理解,顯然由於預設情況下 send 和 transfer 對前向回退函式的 gas 量施加了限制(我不知道這是什麼意思),它本質上會阻止重入)。
由於 call() 轉發所有氣體(同樣,我不確定這是什麼意思)。為了防止重入,我們需要在呼叫 call() 之前更改所有狀態。
有人可以向我解釋轉發 gas 是什麼意思(我的理解是你為每筆交易設置了 gas 限制,如果交易沒有用完所有的 gas,那麼剩餘的 gas 將返回給 msg 發送者)以及如何它與重入攻擊有關嗎?
此外,我嘗試了https://solidity-by-example.org/fallback/中的範常式式碼。有人可以解釋為什麼即使我將氣體限制設置為 3000000,輸出日誌顯示 80000000 氣體?
首先,讓我們創建一個基本的重入漏洞合約:
contract Safe { mapping(address => uint) public userBalances; function deposit() public payable { userBalances[msg.sender] += msg.value; } function withdraw(uint _amount) public { require(userBalances[msg.sender] >= _amount, "low balance"); payable(msg.sender).call({value: _amount}); userBalances[msg.sender] -= _amount; } }
如您所見,我們沒有設置
call
可以使用的最大氣體。這是什麼意思?您可以限制外部函式呼叫可以使用的氣體量。看看這裡。如果你沒有設置這個怎麼辦?它可以使用您的所有氣體(近 1m 氣體)。不好嗎?有可能。備份函式用於合約在收到時應該做什麼ether
(不僅如此,但現在到現在已經足夠了)。我們是一個受歡迎的 DeFi 項目。因此,我們的合約中將有數千個乙太幣。
這個攻擊者創建了一個攻擊我們的安全合約的合約。而這個合約有這個功能:
function attack() public onlyOwner { Safe.deposit({value: 1 ether}); Safe.withdraw(1 ether); }
這個功能基本上是為攻擊者的智能合約充值和提現 1 個乙太幣。而且我們攻擊者的智能合約也有一個回退功能:
fallback() external payable { Safe.withdraw(1 ether); }
好的,讓我們深吸一口氣,一步一步看我們在做什麼:
- 創建攻擊者合約
- 呼叫攻擊功能 - 這意味著存入和提取 1 個乙太幣。
- 當我們嘗試退出時,Safe 合約會嘗試呼叫我們的回退函式。並且該函式也嘗試呼叫withdraw函式。
你能看到嗎?有一個循環。這個循環將一直持續到所有安全合約資金結束。因為會有錯誤。之後,我們將收到所有資金。因為我們的資金沒有減少(因為我們的循環)。
但是,如果您將呼叫函式行更改為:
payable(msg.sender).call({value: _amount, gas: 30000});
可能會沒問題(取決於氣體計算)。但最好的解決方案是先減少 userBalances,然後再發送他的錢。