近年来,智能合约成为了区块链安全的重灾区,从The DAO到BEC,SocialChain,Hexagon,再到EOS漏洞,智能合约安全漏洞频现令不少开发者和企业对区块链望而却步。12月8日,在迅雷链技术沙龙第六站现场,迅雷链底层研发工程师胡登启系统剖析了智能合约安全问题,分析了智能合约中存在的典型漏洞和预防措施,并总结了在合约编程过程中需要注意的几大安全原则,在现场听众间引起了热烈反响。
区块链虚拟机工作原理
关于虚拟机的运行原理,胡登启以EVM为例,讲解了虚拟机底层是如何实现的,让开发者对虚拟机有了更全面的认识。首先,区块链的虚拟机应该包含以下6个特性:1.安全:这也是最重要的,即代码在沙盒中运行,一旦发生错误,可以回滚掉所有更新;2.结果明确:结果确定且没有歧义,在区块链的所有节点执行该逻辑,得到的结果一定是保持一致的;3.简单:操作码低级,结构简单;4.具备特定的能力:虚拟机能处理加密运算,比如支持椭圆曲线算法,能访问交易与链状态,获取blockhash,tx相关内容等等;5.易于优化:支持即时编译(JIT)等;
6.节省空间:虚拟机组件紧凑,便于集成到区块链服务中。
通过分析EVM虚拟机,胡登启总结了通用区块链虚拟机应该是如何运作的:1.首先开发者会使用 c、c++、或者 solidity等高级语言编写一个合约,一般情况下这里包含了很多的复杂的业务逻辑;2.通过编译器llvm,或者solidity编译器,将包含服务业务逻辑的源代码编译成与机器无关的中间代码,也就是我们所说的字节码;3.虚拟机在运行时,首先要通过代码加载器,将字节码读入内存,初始化基础的内存变量;4.然后由虚拟机的执行引擎将一条一条的字节码指令翻译成对应机器平台的底层指令执行;5.执行引擎在执行指令的过程中会使用到临时变量、堆栈区存储空间,或者读写外部文件等,这里统称为运行时数据区。
区块链虚拟机的执行流程
一个通用的虚拟机执行流程可以抽象成下面这个过程:1.首先将字节码加载到内存的代码段,初始化运行环境,这时程序计数器初始化为0。如上图所示,这里有个循环体,循环判断的结束条件是:是否要终止执行?如果判断标记为false,则获取pc执行的操作码,否则退出执行,返回结果;2.获取到pc操作码后,会校验参数,比如参数是否足够,有没有超过堆栈的最大可用空间等;3.接下来会执行具体的指令;4.指令结束后,计数器加一,继续循环,直到程序设置了终止标记位。迅雷链支持图灵完备的EVM虚拟机,胡登启以一个简单的solidity合约为例,为现场开发者分析了EVM虚拟机是如何运行的。EVM虚拟机的执行主流程是在Run函数里面的,在循环里面首先会获取对应的操作码,将操作码关联到对应的指令,这个指令在执行之前会进行一些数据的校验。执行指令完之后,可以判断继续执行指令还是返回退出。
如上图所示,这个合约只有一个Add函数,计算2个uint8变量的和。通过solidity编译器,将合约编译成bytecode. 虚拟机会将bytecode转换成对应的opcode。opcode被表示为预定义的标识符。比如 0x60 对应的opcode是 PUSH1, 0x52 对应的opcode是 MSTORE.
而上面提到的被预定义的标识符,其实就是操作码。EVM中将操作码大致分为四类:第一类是算术运算,包括最基础的加减乘除运算指令;第二类是逻辑运算,包括与、或、非、等于、不等于、大于、小于等;第三类是与区块链状态相关指令,具体是指区块信息如coinbase、区块时间戳、区块序号等,还有交易相关的,如交易的发送者,交易转账金额等指令;第四类是跟存储相关的指令,比如读取虚拟机运行时内存数据,或者存储数据到区块链状态中。每一个操作码都对应一个操作指令。操作指令定义了操作码具体要执行怎样的操作,操作消耗的gas值,操作需要的参数与返回值的个数,还有操作需要额外空间。同时操作指令中还定义了一些标记位,比如终止运算、跳转、是否出错、是否应该返回等。胡登启通过展示最简单的加法操作指令为例,为大家分析了指令结构。EVM虚拟机每个操作指令都会消耗一定的gas值,这么做的目的一方面是为了避免开发者编写无限循环的代码,另一方面是为了给打包交易的节点一点奖励。
智能合约开发的安全准则
不过胡登启指出,EVM只是区块链虚拟机的一种实现方式,而且现行的智能合约还是一个在不断发展的技术,要想运用好这个技术并不容易。事实上,在实践过程中,合约被爆出了各种安全漏洞,这些漏洞可能给项目发行方造成巨大的损失。因为区块链是去中心化的自治系统,往往发现漏洞后,发行方很难去修复这个漏洞,只能任由黑客肆意的攻击。因此要求开发者在编写智能合约时,做更加全面的安全测试,尽量避免包含漏洞的代码发布到区块链系统中。胡登启通过分析智能合约中存在的典型漏洞,向现场听众展示了智能合约安全问题的重要性,并为开发者编写智能合约提供了一些参考。比如,他以今年年初轰动一时的美链BEC合约漏洞为例,讲述了智能合约安全的重要性。BEC是美链公司发布的token,于今年2月上线交易, 4月22日该合约被爆出重大漏洞,导致其token市值几乎归零。
其智能合约中有个批量转账的接口,参数是地址数组和转账token值。如上图的第218行代码,这里有个乘法运算,用以计算一次交易中转移token的总值,计算结果使用uint256类型保存。有过c或者c++开发经验的同学可能会看出,这里存在整数溢出的问题。黑客正是利用了这个漏洞,进行BEC token的巨额增发,最终导致其价值接近归零。胡登启以此作为反面教材,告诫开发者,在合约开发中进行任何数学运算时,都推荐使用SafeMath库,这样可以避免溢出的漏洞。比较讽刺的一点是,BEC合约的其他代码都使用了SafeMath库,唯独漏了这一处。可见代码测试一定要充分,漏了一个点,可能造成巨大损失。通过对另外数起知名安全漏洞案例的分析,胡登启总结了智能合约开发的3条安全准则:1.开发者应深入的理解区块链系统的运行原理。2.希望开发者熟练掌握一门合约语言的特性。3.要做全面的代码测试,不能有侥幸心理。胡登启最后表示,区块链智能合约还是一门比较年轻的技术,需要在开发者的不断实践中去完善与提升。因此在目前阶段,开发者进行智能合约编程时,尤其需要注意规避漏洞,以保证合约具备足够的安全性。