智能合约自动审核指南。 第1部分:准备审核

引言


我们公司从事智能合约的安全审核,使用自动化工具的问题非常严重。 他们在确定可疑地点方面有多少帮助,应使用哪些地点,他们可以做什么以及该领域的工作细节是什么? 这些和相关问题是本文的主题。 资料将尝试在最有趣的代表和配方的帮助下,与真正的合同一起使用,以启动这个极其杂而无用的软件。 一开始我想写一篇文章,但是一段时间后,信息量变得太大了,因此决定写一系列文章,每个自动分析仪一个。 例如, 此处提供了我们将使用的工具的列表,但是如果在编写过程中遇到其他有趣的工具,我将很高兴地描述它们并对其进行测试。


我必须说,审计任务非常有趣,因为 到目前为止,开发人员尚未对算法的经济方面和内部优化给予太多关注。 智能合约的审计增加了一些有趣的攻击媒介,在寻找错误时必须加以考虑。 而且,事实证明,出现了许多用于自动测试的工具:静态分析器,字节码分析器,模糊器,解析器和许多其他好的软件。


本文的目的:促进安全合同代码的分发,并允许开发人员快速轻松地摆脱愚蠢的错误,这些错误通常是最令人讨厌的。 当协议本身完全可靠并解决了严重的问题时,在测试阶段遗忘的愚蠢错误会严重破坏项目的寿命。 因此,让我们至少学习使用允许“少量血液”摆脱众所周知的问题的工具。


展望未来,我必须说,我们在审核中遇到的最常见的关键错误仍然是逻辑上的实现问题,而不是典型的漏洞,例如访问权限,整数溢出,重入。 如果没有经验丰富的开发人员能够对合同的高级逻辑,合同的生命周期,实际操作的方面以及与任务的遵守情况进行审计,而不仅仅是典型的攻击模式,那么就不可能进行大规模,完整的解决方案审计。 高层逻辑经常成为严重错误的来源。


但是,由于粗心大意而导致的警告,典型的漏洞和错误是自动分析仪的命运,它们应该比人们更好地应对这些任务。 这是将要检验的论文。


智能合约代码审核的功能


智能合同代码审核是一个相当具体的领域。 尽管规模很小,以太坊智能合约是一个成熟的程序,可以组织复杂的分支,循环,决策树,甚至要使看似简单的交易自动化,都需要在每个步骤中仔细考虑所有可能的分支。 从这个角度来看,区块链的开发是非常底层的,对资源的要求很高,并且非常让人联想到使用C / C ++和汇编语言进行系统和嵌入式软件的开发。 这就是为什么我们喜欢在采访中看到低级算法,网络堆栈,高负载服务的开发人员,以及处理低级优化和代码审核的每个人。


从开发人员的角度来看,Solidity也很具体,尽管在第一步中,几乎所有程序员都可以轻松阅读它,而且看起来非常简单。 Solidity代码非常易于阅读,任何熟悉C / C ++语法和OOP的开发人员(例如JavaScript)都非常熟悉。


在这里,代码的简单性是生存的关键,没有繁重的工作,因此在工作中使用了低级开发的全部工具-允许有效利用资源,节省内存的算法:Merkle树,Bloom过滤器,“惰性”资源加载,循环展开,手动垃圾收集还有更多。
少量源代码和生成的字节码。


一个单独的智能合约的字节码量受到限制,每个字节消耗一定的电量,并且最大字节数受限制,因此您可以将大约10Kb推入区块链(目前),它将无法工作。 这是一篇关于部署合同成本和天然气成本多少的好文章 。 因此,无法推动太多。 如果您夸大其词,那么“平均”代码将达到数千行。 几十种方法,缺乏聚合和通常复杂的逻辑是合同的极高特征。 一切不合适的内容都要求您在单独的库中选择代码,以更改上载到网络的过程并使之复杂化。 实体开发人员可能很乐意将一堆代码推送到一个合同中,但是他们只需要通过使用自己的存储创建单独的类库来正确安排合同系统即可。 而且,将此类单独的“类”分解为单独的文件很方便,因此,阅读合同的代码非常好,从一开始就一切都井井有条-否则就无法解决。 作为示例,我建议您看一下如何以openzeppelin-solidity制造ERC721


煤气,煤气,煤气


在合同代码的执行中,Gas引入了另一层逻辑,该逻辑需要进行审核。 而且,与传统代码不同,同一代码段可以消耗不同量的气体。 EVM操作码表及其成本对于了解气体限制非常有用。


为了说明为什么您必须花费大量时间来评估气体,请考虑这段伪代码(当然,这是不现实的,用ether在循环中射击是一个坏主意):


//          function fixSomeAccountAction(uint _actionId) public onlyValidator { // … events[msg.sender].push(_actionId); } //   ,           function receivePaymentForSavedActions() { // ... for (uint256 i = 0; i < events[msg.sender].length; i++) { //  actionId   uint actionId = events[msg.sender][i]; //      action uint payment = getPriceByEventId(actionId); if (payment > 0) { paymentAccumulators[msg.sender] += payment; } emit LogEventPaymentForAction(msg.sender, actionId, payment); // … // delete “events[msg.sender][i]” from array } } 

事实是合同中的周期是执行事件[msg.sender] .length次,并且每次迭代都是区块链中的一个条目(transfer()和emit())。 如果数组的长度很小,则循环将完成其十次,为每个动作分配费用。 但是,如果事件[msg.sender]数组很大,则将有很多迭代,并且用尽的气体将达到硬编码的最大气体限制(〜8,000,000)。 事务将失败,并且现在将永远无法工作,因为无法缩短合同中事件[msg.sender]数组的长度。 如果循环不仅计算单位值,而且写入区块链(例如,已支付一些费用,支付操作费用),那么可允许的迭代次数将受到很大限制。 自己判断-上限:8,000,000,记录新的256位值:20,000。 您只能使用一些元数据来保存或更新数百个256位地址的元数据,另一个有趣的部分是编写新值:20,000,并更新现有值:5,000,因此即使在进行合同时,合同的环境完全相同令牌到一个已经有令牌的地址,您在记录上所花费的汽油减少了4倍(5,000对20,000)。


因此,智能合约中的天然气问题与合约的安全性如此紧密相关就不会感到惊讶,因为从实用的角度来看,将资金永久性地固定在合约中的情况与被盗时的情况几乎没有什么不同。 ADD指令消耗3瓦斯和SSTORE(保存到存储):20000的事实意味着,区块链中最昂贵的资源是存储,优化合同代码的任务与在C和嵌入式ASM中进行低级开发的任务有很多共通之处系统,其中存储也是非常有限的资源。


美丽的区块链


这是一个非常积极的段落,说明了为什么从安全角度来看,区块链如此之好,仅适用于审计人员。 合同代码执行的确定性是成功调试和重放错误和漏洞的关键。 从技术上讲,对合同代码的任何调用都可以在任何平台上精确再现,这使得测试可以在任何地方工作并且非常容易支持,并且事件调查是可靠且不可否认的。 现在,我们总是知道谁何时调用哪个函数,带有什么参数,什么代码对其进行处理以及得到的结果是什么。 所有这些都是完全确定的,即 可以在任何地方播放,甚至可以在网页上的JS中播放。 如果我们谈论以太坊,那么任何测试用例都非常容易用方便的JavaScript编写,包括模糊参数,并且在有Node.js的任何地方都可以很好地工作。


但是,所有这些漂亮的词都不应使开发人员感到放松,因为如上所述,最严重的错误是合乎逻辑的,并且对他们而言,执行的确定性是正交的。


建立合同的环境


为了撰写这篇文章,我拿了一份旧的实验性合同来向Smartz设计师预订房屋: https : //github.com/smartzplatform/constructor-eth-booking 。 合同允许您创建对象(公寓或酒店房间)的记录,设置价格和交货日期,之后合同等待付款,如果收到,则修复预订行为,将余额保留在余额中,直到客人进入房间为止。将不会确认输入。 此时,房间的所有者将收到付款。 合同本质上是一个状态机,可以在Booking.sol中查看其状态和转换。 我们非常快地完成了它,在开发过程中对其进行了更改,并且没有进行大量测试,它与新版本的编译器以及相当丰富的内部逻辑相距甚远。 因此,让我们看看分析仪如何应对,发现哪些错误,以及在必要时添加自己的错误。


使用不同版本的solc


不同的分析器必须以不同的方式使用-有些是从docker启动的,有些则使用现成的已编译字节码,并且审计员本人也不必处理几个,而要处理数十个使用不同版本的编译器的早期合同。 因此,您需要能够在主机系统,泊坞窗映像以及松露内部不同地“ solc ”不同版本的solc ,因此,我将为您提供以下几种肮脏的hack选项:


1种方式:松露内


为此,不需要任何技巧,因为 从松露版本5.0.0开始,您可以直接在truffle.js中指定编译器版本,如diff所示


现在松露将下载所需的编译器并运行它。 非常感谢团队为此付出的努力,Solidity是一种年轻的语言,语言的更改非常严重,审核员不接受版本之间的迁移-这样您就可以引入新错误并掩盖旧错误。


方法2:在分析器Docker容器中替换/ usr / bin / solc
如果分析器以Dockerfile的形式分发,则可以在组装Docker映像时通过在Dockerfile中添加一行直接从映像获取所需版本solc的行来替换它,从而将其从网络中拉出并替换/ usr / bin / solc:


 COPY --from=ethereum/solc:0.4.19 /usr/bin/solc /usr/bin 

3种方式:替换/ usr / bin / solc


如果根本没有出路,那么额头上最肮脏的方式是,可以用以下脚本巧妙地替换/ usr / bin / solc二进制文件(不要忘记保存原始文件):


 #!/bin/bash # run Solidity compiler of given version, pass all parameters # you can run “SOLC_DOCKER_VERSION=0.4.20 solc --version” SOLC_DOCKER_VERSION="${SOLC_DOCKER_VERSION:-0.4.24}" docker run \ --entrypoint "" \ --tmpfs /tmp \ -v $(pwd):/project \ -v $(pwd)/node_modules:/project/node_modules \ -w /project \ ethereum/solc:$SOLC_DOCKER_VERSION \ /usr/bin/solc \ "$@" 

它使用正确版本的solc下载并缓存solc ,转到当前目录,并通过传递的参数运行/usr/bin/solc 。 这不是一个很好的方法,但是也许对于某些任务它会很适合您。


展平代码


现在让我们找出来源。 当然,从理论上讲,自动分析器(尤其是用于静态源分析)应该收集合同,提取所有依赖项,将所有内容组合成一个整体并进行分析。 但是,正如我已经说过的那样,版本之间的更改可能很严重,并且我不断发现需要在docker中放置一个附加目录,在路径内对其进行配置,所有这些以便正确地提取必要的导入。 有些分析器了解所有内容,第二种则不了解;因此,对于只吃一个文件的分析器来说,将所有内容合并到一个文件中并仅对其进行分析更为方便,以免遭受抛出其他目录的困扰。


为此,请使用常规松露平整器


这是一个标准的npm模块,使用非常简单:


 truffle-flattener contracts/Booking.sol > contracts/flattened.sol 

https//github.com/trailofbits/slither
如果您需要以某种方式自定义拼合,则可以编写自己的拼合器,例如,在我们使用基于python的选项之前: https : //github.com/mixbytes/solidity-flattener


让我们开始分析。


在同一个老人https://github.com/smartzplatform/constructor-eth-booking的示例中, 我们继续进行分析。 该合同指示编译器的旧版本为“ 0.4.20”,我有意采用该旧合同来解决编译器的问题。 由于自动分析器(例如,研究字节码)可能依赖于此版本的solc,因此情况变得更糟,此处版本之间的差异会极大地影响结果甚至破坏所有功能。 因此,即使您正在使用最新版本进行所有犹太洁食,您仍然可以运行已调整到编译器先前版本的分析器。
编译并运行测试


首先,只需从github提取项目并尝试编译。:


 git clone https://github.com/smartzplatform/constructor-eth-booking.git cd constructor-eth-booking npm install truffle compile 

当然,您对编译器版本有疑问。 此外,自动分析器也存在这些问题,因此请使用任何方法来获取0.4.20编译器并构建项目。 我刚刚在truffle.js中注册了必需的编译器版本,并且如上所述进行了组装。


也跑


 truffle-flattener contracts/Booking.sol > contracts/flattened.sol 

如有关扁平化的段落中所述contracts/flattened.sol我们将为contracts/flattened.sol提供给不同的分析器进行分析
绪论


现在,有了solc并具有使用任意版本的solc ,您就可以开始分析了。 我将省略运行松露和测试的问题,有关此问题的文档很多,请自行整理。 当然,测试必须成功运行。 同样,为了检查逻辑,审计人员通常必须添加自己的测试,检查可能泄漏的地方,例如,检查数组边界处的合同功能,包括测试中的所有变量,甚至包括严格用于数据存储的变量等。 除了这只是我们公司向市场提供的产品外,还有很多建议,因此逻辑研究纯属人工。


我们将回顾从我们的角度来看有趣的分析器,尝试将我们的合同纳入其中,并且我们将人为地引入漏洞,以评估自动分析器将如何对它们做出反应。 下一篇文章将专门介绍Slither分析器,通常,该行动计划大致如下:


第1部分。简介。 Solidity的编译,展平,版本(本文)
第2部分。
第3部分。 秘银
第4部分。Manticore
第5部分。
第6部分。未知工具1
第7部分。未知工具2


之所以获得这套分析器,是因为对于审核员而言,能够使用不同类型的分析(静态和动态)非常重要,并且它们需要完全不同的方法。 我们的任务是学习如何在每种类型的分析中使用基本工具,并了解何时使用哪种工具。


也许在详细研究的过程中,将出现新的候选人供考虑,或者文章的顺序将改变,所以请保持关注。 要转到下一部分, “单击此处”

Source: https://habr.com/ru/post/zh-CN438336/


All Articles