5.1.2 재진입 공격
(A)콘트랙트에서 (B)콘트랙트로의 어떠한 상호작용 및 이더의 전송은 (B)로 제어권을 넘겨주게 된다. 이러한 사실은 B가 상호작용이 끝나기 전에 다시 A를 호출할 수 있는 상황을 초래한다. 예를 들어, 다음 코드는 버그를 포함하고 있다(완전한 코드는 아닌 요약본).
pragma solidity ^0.4.0;
// 이 콘트랙트는 버그를 포함하고 있으니 절대 사용하지 말 것!!!
contract Fund {
/// 콘트랙트의 이더 지분 정보 매핑
mapping(address => uint) shares;
/// 지분 인출
function withdraw() {
if (msg.sender.send(shares[msg.sender])
shares[msg.sender] = 0;
}
}
이 코드는 (send를 쓰기 때문에; send함수는 gas소비량이 정해져 있다)이더 전송에 가스 소비를 제한하고 있기 때문에 문제가 심각하지는 않지만 여전히 약점에 노출되어 있다. 이더의 전송은 항상 코드를 실행시키기 때문에 수신자는 withdraw함수를 다시 실행하여 콘트랙트의 모든 이더를 훔쳐갈 수 있다.
재진입공격을 막기 위해서 아래 방식으로 작성된 Check-Effects-Interaction패턴을 사용해야 한다.
pragma solidity ^0.4.0;
contract Fund {
/// 콘트랙트의 이더 지분을 매핑
mapping(address => uint) shares;
/// 지분을 인출
function withdraw() {
var share = shares[msg.sender];
shares[msg.sender] = 0;
if (!msg.sender.send(share))
throw;
}
}
주목할 점은 재진입이 이더 전송간 이뤄지는 것이 아니라 다른 콘트랙트의 함수를 호출할 때 이뤄진다는 것이다.(Note that re-entrancy is not only an effect of Ether transfer but of any function call on another contract.) 더해서 여러 개의 콘트랙트가 연관된 상황도 고려해야 한다. 호출된 콘트랙트는 다른 콘트랙트의 상태를 변화시킬 수 있기 때문이다.