solidity编写智能合约的安全漏洞问题(一)

回顾 3 个底层调用 call()delegatecall()callcode() 和 3 个转币函数 call.value()()send()transfer()

- call()

call() 用于 Solidity 进行外部调用,例如调用外部合约函数 

.call(bytes4(keccak("somefunc(params)"), params)),外部调用 call() 返回一个 bool 值来表明外部调用成功与否:

- delegatecall()

除了 delegatecall() 会将外部代码作直接作用于合约上下文以外,其他与 call() 一致,同样也是只能获取一个 bool 值来表示调用成功或者失败(发生异常)。

- callcode()

callcode() 其实是 delegatecall() 之前的一个版本,两者都是将外部代码加载到当前上下文中进行执行,但是在 msg.sender 和 msg.value 的指向上却有差异。

例如 Alice 通过 callcode() 调用了 Bob 合约里同时 delegatecall() 了 Wendy 合约中的函数,这么说可能有点抽象,看下面的代码:

如果还是不明白 callcode() 与 delegatecall() 的区别,可以将上述代码在 remix-ide 里测试一下,观察两种调用方式在 msg.sender 和 msg.value 上的差异。

- call.value()()

在合约中直接发起 TX 的函数之一(相当危险),

- send()

通过 send() 函数发送 Ether 失败时直接返回 false;这里需要注意的一点就是,send() 的目标如果是合约账户,则会尝试调用它的 fallbcak() 函数,fallback() 函数中执行失败,send() 同样也只会返回 false。但由于只会提供 2300 Gas 给 fallback() 函数,所以可以防重入漏洞(恶意递归调用)。

- transfer()

transfer() 也可以发起 Ether 交易,但与 send() 不同的时,transfer() 是一个较为安全的转币操作,当发送失败时会自动回滚状态,该函数调用没有返回值。同样的,如果 transfer() 的目标是合约账户,也会调用合约的 fallback() 函数,并且只会传递 2300 Gas 用于 fallback() 函数执行,可以防止重入漏洞(恶意递归调用)。

这里以一个简单的示例来说明严格验证底层调用返回值的重要性:

function withdraw(uint256 _amount) public {
    require(balances[msg.sender] >= _amount);
    balances[msg.sender] -= _amount;
    etherLeft -= _amount;
    msg.sender.send(_amount);  // 未验证 send() 返回值,若 msg.sender 为合约账户 fallback() 调用失败,则 send() 返回 false
}

上面给出的提币流程中使用 send() 函数进行转账,因为这里没有验证 send() 返回值,如果 msg.sender 为合约账户 fallback() 调用失败,则 send() 返回 false,最终导致账户余额减少了,钱却没有拿到。

 

这里先提供一些链接便于参考学习:

https://paper.seebug.org/661/

https://news.bitcoinworld.com/a/2599?locale=zh_HANT

https://github.com/ConsenSys/smart-contract-best-practices/blob/master/README-zh.md

后续会有更多有关细节方面的介绍......

你可能感兴趣的:(区块链,智能合约,区块链技术)