安全考虑

翻译原文

date:20170728

按照以往的经验来开发软件通常会比较简单,但是如果用前无古人的方法来写就会有些难度。

在Solidity中,这尤其重要,因为你可以用智能合约来处理token或者更有甚者其他有价值的东西。另外,智能合约的每次执行都是公开的,而且源码通常是公开的。

当然你总是会关心它的花费:你可以把智能合约跟网站服务作比较,他们都是面向公众的(因此,也会有恶意的黑客)并且可能是开源的。如果你只想把你的购物清单保存在网站服务中,你不必关系很多,如果你用网站服务来管理你的银行账户,你就要多加小心了。

这个章节会罗列出一些陷阱和常见安全建议,当然,永远不会补充完整。所以,你也应该心里有数,尽管你的智能合约不会出现问题,但是编译器或者平台本身可能会出现问题。编译器的安全相关问题都罗列在已知bug列表中,也是机器可读的。注意,针对Solidity 编译器的代码生成器,有一个bug赏金程序。

也请你帮组我们来完善这个开源文档(尤其是,多写几个例子)。

陷阱

私有信息和随机性

你使用智能合约的所有事情,都是公共可见的,即使是局部变量和标记为private的状态变量。

如果你不想矿工作弊,在智能合约中使用随机数是很好的技巧。

重入(RE-Entrancy)

任何合约A与合约B的交互以及任何发送以太币都会把程序控制权交给合约B。这使得合约B在交互结束之前,可以回调A。举个例子,下面的代码有bug(这只是代码片段,不是完整的合约代码):

pragma solidity ^0.4.0;

// 这个合约包含bug,请不要使用
contract Fund {
    /// 映射合约的以太币股份
    mapping(address => uint) shares;
    /// 取回你的股份
    function withdraw() {
        if (msg.sender.send(shares[msg.sender]))
            shares[msg.sender] = 0;
    }
}

这个问题不是很严重,因为send也有gas限制,但是依旧暴露出一个问题:以太币交易总是伴随着代码执行,所以接收者可能是一个会执行withdraw函数的合约。这会使得它可以多次取回金额,并且可以取回该合约的所有以太币。

为了防止重入,你可以使用下面的检查影响交互模式:

pragma solidity ^0.4.11;

contract Fund {
    /// 映射合约的以太币股份
    mapping(address => uint) shares;
    /// 取回股份
    function withdraw() {
        var share = shares[msg.sender];
        shares[msg.sender] = 0;
        msg.sender.transfer(share);
    }
}

注意,重入不仅对以太币交易有影响,而且可以是合约里的任何函数。另外,你必须考虑合约账户的多合约交互情况。一个被调用的合约可能会改变调用者的状态。

gas限制和循环

循环不会有固定次数的遍历,例如,循环依赖storage变量,一定要小心:因为gas的限制,交易只能消耗特定数量的gas。要么指定,要么只是执行正常的操作,循环的遍历次数必须在gas限制以内,gas不足会导致整个合约在某个时刻熄火。这不支持只是从区块链中读取数据的constant函数。这些函数仍然可能被其他合约调用作为on-chain操作的一部分,并且失败。请在你的合约中指出这种情况。

发送和接收以太币
  • 目前,合约和外部账户,都不能阻止某人给他们发送以太币。合约可以交互和拒绝常规的交易,但是可以有很多方式来移动以太币,而无需消息调用。一种方法是简单的在某个合约地址上挖矿,第二种方法是使用selfdesruct(x)
  • 如果一个合约接收以太币(没有调用函数),回调函数就会执行。如果没有回调函数,以太币就会被拒绝(通过抛出异常)。在执行回调函数过程中,合约只依赖于当时所需的“gas薪金”(2300gas)。但是该薪金不足以访问storage。为了保证你的合约可以接受以太币,要检查回调函数所需的gas数目(例子在Remix的详情章节)。
  • 接收合约使用addr.call.value(x)()会传递更多的gas。这个函数只有在传递所有剩余的gs和对接受者开放其他更昂贵的操作(并且它在操作失败的情况下只返回失败代码,并不会自动传递错误)的情况下,和addr.transfer(x)的表现一致。这可能包含对调用方的回调或者其他你不期望的状态改变。所以这为诚实的或者恶意的用户提供了很高的灵活性。
  • 如果使用address.transfer来发送以太币,下面几个要点要特别注意:
  1. 如果接收者是一个合约,这会引发回调函数的执行,并且能够,返过来回调调用者的函数。
  2. 如果栈深度超过1024,发送以太币的操作会失败。因为调用者完全控制了调用深度,他可以强制关闭交易。这个是合约的能力,或者使用send并且保证总是检查他的返回值。更好的是,合约按照一定的模式来写,使得接收者可以取回以太币。
  3. 发送以太币可能会失败,因为执行接收者合约需要更多的gas(使用require,assert,revert,throw或者因为操作本来就很昂贵)-它会返回“gas不足”(OOG)。如果你使用transfer或者send之后有对返回值进行检查,这会给接收者提供中断交易的方法。再说一次,用取回模式替代发送模式是很好的练习。
回调深度

外部函数调用可能在任何时候都会失败,应为他们的最大调用栈深为1024.这种情况下,Solidity会抛出一个异常。恶意的黑客可能会在调用你的合约之前把栈深提高。

注意,send()函数在调用栈被耗尽的情况下,不会抛出异常,而是返回false。底层函数.call().callcode().delegatecall()的行为都是一样的。

tx.orgin

永远不要使用tx.orgin来验证。我们假定你有如下的钱包合约:

pragma solidity ^0.4.11;

// 这个合约包含bug - 不要使用
contract TxUserWallet {
    address owner;

    function TxUserWallet() {
        owner = msg.sender;
    }

    function transferTo(address dest, uint amount) {
        require(tx.origin == owner);
        dest.transfer(amount);
    }
}

现在有人欺骗你,让你把以太币发送到这个攻击钱包的地址上:

pragma solidity ^0.4.11;

interface TxUserWallet {
    function transferTo(address dest, uint amount);
}

contract TxAttackWallet {
    address owner;

    function TxAttackWallet() {
        owner = msg.sender;
    }

    function() {
        TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
    }
}

如果你的钱包对msg.sender进行验证,它会获取到攻击的钱包地址,而不是所有者的地址。通过验证tx.orgin,他会得到初始地址,攻击者会获取到你的所有金额。

次要详情
  • for (var i = 0; i < arrayName.length; i++) { ... }中,i的类型为uint8。因为它是用来保存0的最小的类型。如果数组有大于255个元素,循环会被终止。
  • 目前,对有constant修饰的函数编译器不会强制要求不可变。另外EVM也没有强制。所以虽然合约中声明为静态,但是依旧会被改变。
  • 不占据完整32位字节的类型会包含“高位脏数据”。如果你访问msg.data,这一点尤其重要-它暴露了一个可扩展性问题:针对函数f(uint8 x),你可以使用参数0xff0000010x00000001来欺骗交易。这两个参数传进函数的时候,x都会觉得是1,但是msg.data就会不同。如果任何地方使用keccak256(msg.data),就会有不同的结果。

建议

限制发送的以太币数目

限制保存在智能合约里的以太币(或者其他代币)的数量。如果你的源码,或者平台或者编译器出现问题,这些钱币可能会丢失。如果你想要限制你的损失,那就限制钱币数目。

保证它小型和模块化

让你的合约保持小巧和容易理解。把不相关的函数移到其他合约或者库中。针对提高源码质量的通常的建议是:限制局部变量的数量,函数的长度等等。给你的函数提供文档,那么其他人可以知道你的意图,并且知道代码是否是这么做了。

使用检测交互效果模式

很多函数会首先检查代码(调用函数方的参数是否符合要求,是否发送了足量的以太币,用户是否有令牌等)。这些检查应该先执行。

第二步,如果所有的检测通过了,当前合约就会修改这些状态。和其他合约的交互在任何函数都应该放在最后一步。

早期合约发布一些功能并等待外部函数调用来返回非错误状态。这通常是一个严重的错误,因为上面所论述的重入问题。

注意,对已知合约的调用,也可能引起未知合约的调用。所以只使用这个模式可能会更好。

包含一个失败安全模式

当你的系统完全去中心化的时候,会移除所有的中间人。引入失败安全机制可能是一个好的想法,尤其是对新的代码:

你可以在智能合约中添加一个函数,用来做“是否有以太币泄露”的自检,“是否所有花费跟合约的余额一致”或者类似的事情。需要注意的是,你不能为这个检查花费太多的gas。所有这里可能需要off-chain计算。

如果自我检查失败了,合约会自动转换到安全模式,例如,关闭大部分功能,移交控制权来修复了bug的,并且信任的第三方或者只是把合约转换为简单的“把钱还给我”合约。

正规化校验

使用正规化校验,他可能实现自动数学验证你的源码包含一种特定的正规要求。要求也是正规化的(就像源码一样),但是通常会更加简单。

需要注意的是正规化校验本身只能帮助你理解“你做了什么”(要求)和“你怎么做”(你的实现)之间的区别。你需要检查要求是不是你所期望的并且没有漏过任何不期望的效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 简介 不管你们知不知道以太坊(Ethereum blockchain)是什么,但是你们大概都听说过以太坊。最近在新...
    Lilymoana阅读 3,891评论 1 22
  • 本文翻译自:https://github.com/ConsenSys/smart-contract-best-pr...
    tolak阅读 4,945评论 4 21
  • 原文链接date:20170710 Solidity中合约的概念和其他面向对象语言中的类差不多。他们都有状态变量来...
    gaoer1938阅读 980评论 0 0
  • (独享)城里大巴真的大,城市建设快速化。今天汽车人独享,城市轨道四处达。一马平川山那头,一路奔弛到小康。人生无常心...
    甘朝武阅读 145评论 0 0
  • 我想采一千片幸运草 ­ 把我想给你的幸福攒够 ­ 送给你 我愿采一千片幸运草 ­ 放在星空下 ­ 祭奠我们流产的爱...
    潘林虎阅读 323评论 1 1