龙空技术网

智能合约中的“座霸”——存储器局部变量未初始化

青年深先生 50

前言:

当前小伙伴们对“未初始化的局部变量”可能比较关怀,同学们都需要知道一些“未初始化的局部变量”的相关内容。那么小编也在网络上搜集了一些对于“未初始化的局部变量””的相关内容,希望小伙伴们能喜欢,看官们一起来学习一下吧!

针对区块链安全问题,成都链安科技团队每一周都将出智能合约安全漏洞解析连载,希望能帮助程序员写出更加安全牢固的合约,防患于未然。

行身践规矩,甘辱耻媚灶。——韩愈

前情提要

上回讲到,区块链游戏江山如画,

安全防护未规划,

一片残阳西挂。

我们在上一期的区块链游戏漏洞的汇总和分析中将目前游戏合约出现的问题与前几期的漏洞连载分析进行了联动,发现游戏合约的漏洞很大一部分是在重复之前代币合约的重大错误。被鲜亮外衣包裹的游戏合约在吸引更多眼球的同时,也需要对安全问题提高重视,才能获得更长远的发展。

本期话题

第七回, 本地变量存储措手不及,意外变量覆盖易帜拔旗

最近新闻上的“座霸”事件,在社会中引起了强烈的反响,一个理应对号入座的乘车环境,在某些人不守规矩的情况下,导致买了票的乘客没有座位,以及车厢内的秩序混乱。

成都链安科技的小伙伴们在办公室对此事件各抒己见,热烈讨论,一致反对这种无理的行为。但是在讨论之余,同时也连联想到,没有对号入座而引起混乱这个问题,其实在智能合约漏洞问题当中有类似的情况。

基础小知

大家都清楚,谈到存储,变量被存储时都会被分配一个存储位置。这个位置可以被理解为乘车时的座位。在智能合约语言 Solidity当中,存在Storage(存储器)和 Memory(内存)两个不同的概念。Storage变量是指永久存储在区块链中的变量。Memory变量是临时的,这些变量在外部调用结束后会被移除。

但是Solidity目前对复杂的数据类型,比如array(数组)和struct(结构体),在函数中作为局部变量时,会默认储存在Storage当中。

此外,Solidity对于状态变量,存储次序一般是按照出现的先后顺序依次排列的。这些状态变量的位置就相当于它们的座位。

问题出在哪

Solidity与传统语言有个很明显的不同,就是允许定义一个指向Storage的引用。未初始化的外部指针(引用)会默认指向起始地址,如果不加以初始化,直接进行赋值,0地址上的状态变量就会被覆写。

拿乘坐列车打个比方,第一批乘客上车时没有安排相应的座位号,于是大家都是按照上车顺序,从前往后坐。到了新的目的地,第二批乘客上车时没有被指定座位号,坐剩余的座位,又想从前往后坐,第一批乘客原本坐在座位上,现在直接被“座霸”赶走,无位可坐。

合约中的“坐霸”实例

开发中的缺一手成都链安科技技术团队翻开之前的漏洞案例册,从上面发现这种漏洞原先体现为由于开发者疏忽遗留出的已被攻击者利用的漏洞,例如有关安全公司发现的BancorLender相关的指针问题。

如图所示,状态变量agreements一开始被声明在第一个黄色框内,进入起始位置slot 0x00。

第二个黄色框框是在函数offerToLend()中试图声明一个新的局部变量agreement,但其未做初始化处理,所以起始位置slot 0x00会被新的局部变量agreement占据。更具体些讲,从粉红色划线处开始的后面三项赋值操作都会覆盖slot 0x00到slot 0x03上原有的值。最后导致了代码逻辑紊乱,功能无法正常实现[1]。

蜜罐中的留一手

此外,联系上一期我们提到的游戏合约,这个漏洞不出意外的在游戏合约中也出现了,但是出现的形式是蜜罐,蜜罐我们之前也提到过,是故意放置明显的破绽让略懂技术的玩家以为有机可趁,但实际上更深处有合约拥有者留给自己的不公平获利操作空间。所以我们将这种情况归为此类漏洞的第二种具体情况。

案例来源于一个名为OpenAddressLottery的博彩合约。

这部分代码中的“s”被声明但是并没有做相应的初始化处理,所以实际上之后的赋值操作都会覆盖原有地址上重要的值。

会代替哪些值呢?我们来看谁“坐在”最初始Storage地址上:

所谓的可预测的最终答案LuckyNumber占据的最初始的位置,所以实际上是会被tx.gasprice*7所覆盖的,而address owner会被msg.sender代替,但这两个值实际上是一样的。

由于luckyNumberOfAddress的结果以模8(二进制)的形式计算,而被覆盖后tx.gasprice*7真实的结果一定会大于7,所以玩家想赢是绝对不可能的[2]。

其中我们还要注意一点,第一部分代码中,require(msg.send==owner),表示只有合约拥有者才能调用这个能覆盖原有值的函数,所以合约的蜜罐意图非常明显。

最终的结果也符合了我们的推断:

合约创建者在转出所有参与者的资金后,启动自毁,逃之夭夭。

表现形式总结与修复建议

总结上述具体案例的情况,我们可以说:未初始化的存储器局部变量可以指向合约中的状态变量,从而导致故意(即开发人员故意将它们放在那里进行攻击)或无意的漏洞。

我们将一些典型的默认储存在Storage中的变量分为结构体(struct)和数组(Array)展示出错误范例[3]。

典型结构体(struct)错误范例:

当输入_name="0x0000000000000000000000000000000000000000000000000000000000000001"(63个0),地址任意地址时,会覆盖unlocked的值,使其变为true。

典型数组(Array)错误范例:

当输入elements=["0x0000000000000000000000000000000000000000000000000000000000000001"](63个0),会覆盖frozen的值,使其变为true。

漏洞修复建议

Remix-ide等编译器会对未初始化的存储器局部变量进行告警,开发人员不能忽略这个警告,在声明变量时,应对这些存储器局部变量进行初始化,或者根据其使用情况,将其安排在暂时的存储空间Memory上,避免安全漏洞。

良好的秩序,良好的心态

本期介绍的漏洞,是由于Solidity语言的默认存储规则,以及引用未初始化变量的特殊性共同导致的。在传统语言当中,这个情况会在编译器当中报错,无法通过。目前的Solidity版本(0.4.24)却没有进行相同严格的禁止,只会在编译器中给出告警。所以成都链安科技团队在这里针对智能合约开发和使用两方面再次强调:

遵守合约开发规范,缜密筹备安全防护,是我们屡次三番提到的合约开发精神,在区块链这个新兴的技术应用时遵守规范、周全规划,才能更好的帮助新兴技术稳步发展。对于蓄谋欺骗大众的投机合约,当发现破绽时,一定要提防合约创建者的蜜罐手段,多留一个心眼,避免上当受骗。

目前的区块链是一个尚未成熟,有待发展的产业,追逐机遇的同时,做到冷静思考,心态平和是成功的必备素养,请大家给变量分配位置的同时,也给自己的心态调整位置。

标签: #未初始化的局部变量