Skip to content
This repository has been archived by the owner on Feb 14, 2025. It is now read-only.

Makes Manticore an optional dependency #51

Merged
merged 18 commits into from
Feb 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9ffeb19
Remove Manticore as a requirememt (#48)
ESultanik Nov 13, 2018
712ebca
Install Etheno with Manticore in Docker (#48)
ESultanik Nov 13, 2018
6708cd5
Do not complain if Manticore is not installed (#48)
ESultanik Nov 13, 2018
5b33b46
Updated with Manticore being an optional requirement (#48)
ESultanik Dec 7, 2018
2324705
Merge branch '49-update-examples' into 48-optional-manticore
ESultanik Jan 24, 2019
afff680
Added the manticore client module (#48)
ESultanik Jan 24, 2019
ebc6319
Only reassign manticore loggers (#48)
ESultanik Jan 24, 2019
197cf5c
Fixed a bug when no args are passed
ESultanik Jan 24, 2019
d4fa934
Merge branch '49-update-examples' into 48-optional-manticore
ESultanik Jan 24, 2019
65699cd
Merge branch '49-update-examples' into 48-optional-manticore
ESultanik Jan 24, 2019
3631af2
Merge branch '49-update-examples' into 48-optional-manticore
ESultanik Jan 26, 2019
dee5e7f
Added debug statements for tracking low level RPC calls
ESultanik Feb 6, 2019
ee1a4d5
Change module import ordering to ensure our logger is loaded before M…
ESultanik Feb 8, 2019
c3f9179
Add an import to ensure that we override Manticore's logger (#48 #52)
ESultanik Feb 8, 2019
4d4225f
Override `logger.getLogger` to return a wrapped instance (#52)
ESultanik Feb 8, 2019
dd572cb
A horrible hack while we wait for https://github.com/trailofbits/mant…
ESultanik Feb 8, 2019
bc26f14
Update the CHANGELOG with Manticore fixes (#48)
ESultanik Feb 8, 2019
40ed206
Up the version number
ESultanik Feb 8, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

The format is based on [Keep a Changelog](http://keepachangelog.com/).

## [Unreleased](https://github.com/trailofbits/manticore/compare/0.2.2...HEAD)
## [Unreleased](https://github.com/trailofbits/etheno/compare/v0.2.0...HEAD)

### 0.2.1 — 2019-02-07

Bugfix release.

- Manticore is now an optional requirement
- Improvements and bugfixes to the logger integration with Manticore
- Added a workaround to the examples for a bug in Truffle

## 0.2.0 — 2018-11-02

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ COPY etheno/*.py /home/etheno/etheno/etheno/
RUN mkdir -p /home/etheno/examples
COPY examples /home/etheno/examples/

RUN cd etheno && pip3 install --user .
RUN cd etheno && pip3 install --user '.[manticore]'

USER root

Expand Down
19 changes: 16 additions & 3 deletions etheno/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@
from .client import RpcProxyClient
from .differentials import DifferentialTester
from .echidna import echidna_exists, EchidnaPlugin, install_echidna
from .etheno import app, EthenoView, GETH_DEFAULT_RPC_PORT, ManticoreClient, ETHENO, VERSION_NAME
from .etheno import app, EthenoView, GETH_DEFAULT_RPC_PORT, ETHENO, VERSION_NAME
from .genesis import Account, make_accounts, make_genesis
from .synchronization import AddressSynchronizingClient, RawTransactionClient
from .utils import clear_directory, decode_value, find_open_port, format_hex_address, ynprompt
from . import Etheno
from . import ganache
from . import geth
from . import logger
from . import manticoreutils
from . import parity
from . import truffle

try:
from .manticoreclient import ManticoreClient
from . import manticoreutils
MANTICORE_INSTALLED = True
except ModuleNotFoundError:
MANTICORE_INSTALLED = False

def main(argv = None):
parser = argparse.ArgumentParser(description='An Ethereum JSON RPC multiplexer and Manticore wrapper')
parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging from within the web server')
Expand Down Expand Up @@ -238,12 +244,19 @@ def main(argv = None):

manticore_client = None
if args.manticore:
if not MANTICORE_INSTALLED:
ETHENO.logger.error('Manticore is not installed! Running Etheno with Manticore requires Manticore version 0.2.2 or newer. Reinstall Etheno with Manticore support by running `pip3 install --user \'etheno[manticore]\'`, or install Manticore separately with `pip3 install --user \'manticore\'`')
sys.exit(1)
new_enough = manticoreutils.manticore_is_new_enough()
if new_enough is None:
ETHENO.logger.warning(f"Unknown Manticore version {manticoreutils.manticore_version()}; it may not be new enough to have Etheno support!")
elif not new_enough:
ETHENO.logger.error(f"The version of Manticore installed is {manticoreutils.manticore_version()}, but the minimum required version with Etheno support is 0.2.2. We will try to proceed, but things might not work correctly! Please upgrade Manticore.")
manticore_client = ManticoreClient()
ETHENO.add_client(manticore_client)
if args.manticore_max_depth is not None:
manticore_client.manticore.register_detector(manticoreutils.StopAtDepth(args.manticore_max_depth))
manticore_client.manticore.verbosity(getattr(logger, args.log_level))
manticore_client.reassign_manticore_loggers()

if args.truffle:
truffle_controller = truffle.Truffle(parent_logger=ETHENO.logger)
Expand Down
142 changes: 7 additions & 135 deletions etheno/etheno.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@
VERSION_ID=67

import logging
import sha3
from threading import Thread
import time

from flask import Flask, g, jsonify, request, abort
from flask.views import MethodView

from manticore.ethereum import ManticoreEVM
import manticore

from . import logger
from . import threadwrapper
from .client import EthenoClient, JSONRPCError, RpcProxyClient, SelfPostingClient, DATA, QUANTITY, transaction_receipt_succeeded, jsonrpc
from .client import JSONRPCError, SelfPostingClient
from .utils import format_hex_address

app = Flask(__name__)
Expand All @@ -30,17 +25,6 @@ def to_account_address(raw_address):
addr = "%x" % raw_address
return "0x%s%s" % ('0'*(40 - len(addr)), addr)

def encode_hex(data):
if data is None:
return None
elif isinstance(data, int) or isinstance(data, long):
encoded = hex(data)
if encoded[-1] == 'L':
encoded = encoded[:-1]
return encoded
else:
return "0x%s" % data.encode('hex')

_CONTROLLER = threadwrapper.MainThreadController()

@app.route('/shutdown')
Expand All @@ -53,124 +37,6 @@ def _etheno_shutdown():
shutdown()
return ''

class ManticoreClient(EthenoClient):
def __init__(self, manticore=None):
self._assigned_manticore = manticore
self._manticore = None
self.contracts = []
self.short_name = 'Manticore'
self._accounts_to_create = []

@property
def manticore(self):
if self._manticore is None:
if self._assigned_manticore is None:
# we do lazy evaluation of ManticoreClient.manticore so self.log_directory will be assigned already
if self.log_directory is None:
workspace = None
else:
workspace = self.log_directory
self._assigned_manticore = ManticoreEVM(workspace_url=workspace)
self._manticore = threadwrapper.MainThreadWrapper(self._assigned_manticore, _CONTROLLER)
self._finalize_manticore()
return self._manticore

def _finalize_manticore(self):
if not self._manticore:
return
for balance, address in self._accounts_to_create:
self._manticore.create_account(balance=balance, address=address)
self._accounts_to_create = []
self.logger.cleanup_empty = True

def create_account(self, balance, address):
self._accounts_to_create.append((balance, address))
self._finalize_manticore()

def reassign_manticore_loggers(self):
# Manticore uses a global to track its loggers:
for name in manticore.utils.log.all_loggers:
manticore_logger = logging.getLogger(name)
for handler in list(manticore_logger.handlers):
manticore_logger.removeHandler(handler)
logger.EthenoLogger(name, parent=self.logger, cleanup_empty=True)

@jsonrpc(from_addr = QUANTITY, to = QUANTITY, gas = QUANTITY, gasPrice = QUANTITY, value = QUANTITY, data = DATA, nonce = QUANTITY, RETURN = DATA)
def eth_sendTransaction(self, from_addr, to = None, gas = 90000, gasPrice = None, value = 0, data = None, nonce = None, rpc_client_result = None):
if to is None or to == 0:
# we are creating a new contract
if rpc_client_result is not None:
tx_hash = rpc_client_result['result']
while True:
receipt = self.etheno.master_client.post({
'id' : "%s_receipt" % rpc_client_result['id'],
'method' : 'eth_getTransactionReceipt',
'params' : [tx_hash]
})
if 'result' in receipt and receipt['result']:
address = int(receipt['result']['contractAddress'], 16)
break
# The transaction is still pending
time.sleep(1.0)
else:
address = None
contract_address = self.manticore.create_contract(owner = from_addr, balance = value, init=data)
self.contracts.append(contract_address)
self.logger.info(f"Manticore contract created: {encode_hex(contract_address.address)}")
#self.logger.info("Block number: %s" % self.manticore.world.block_number())
else:
self.manticore.transaction(address = to, data = data, caller=from_addr, value = value)
# Just mimic the result from the master client
# We need to return something valid to appease the differential tester
return rpc_client_result

@jsonrpc(TX_HASH = QUANTITY)
def eth_getTransactionReceipt(self, tx_hash, rpc_client_result = None):
# Mimic the result from the master client
# to appease the differential tester
return rpc_client_result

def multi_tx_analysis(self, contract_address = None, tx_limit=None, tx_use_coverage=True, args=None):
if contract_address is None:
for contract_address in self.contracts:
self.multi_tx_analysis(contract_address = contract_address, tx_limit = tx_limit, tx_use_coverage = tx_use_coverage, args = args)
return

tx_account = self.etheno.accounts

prev_coverage = 0
current_coverage = 0
tx_no = 0
while (current_coverage < 100 or not tx_use_coverage) and not self.manticore.is_shutdown():
try:
self.logger.info("Starting symbolic transaction: %d" % tx_no)

# run_symbolic_tx
symbolic_data = self.manticore.make_symbolic_buffer(320)
symbolic_value = self.manticore.make_symbolic_value()
self.manticore.transaction(caller=tx_account[min(tx_no, len(tx_account) - 1)],
address=contract_address,
data=symbolic_data,
value=symbolic_value)
self.logger.info("%d alive states, %d terminated states" % (self.manticore.count_running_states(), self.manticore.count_terminated_states()))
except NoAliveStates:
break

# Check if the maximun number of tx was reached
if tx_limit is not None and tx_no + 1 >= tx_limit:
break

# Check if coverage has improved or not
if tx_use_coverage:
prev_coverage = current_coverage
current_coverage = self.manticore.global_coverage(contract_address)
found_new_coverage = prev_coverage < current_coverage

if not found_new_coverage:
break

tx_no += 1

class EthenoPlugin(object):
_etheno = None
logger = None
Expand Down Expand Up @@ -299,6 +165,8 @@ def estimate_gas(self, transaction):
return None

def post(self, data):
self.logger.debug(f"Handling JSON RPC request {data}")

for plugin in self.plugins:
plugin.before_post(data)

Expand Down Expand Up @@ -332,6 +200,7 @@ def post(self, data):
ret = e

self.rpc_client_result = ret
self.logger.debug(f"Result from the master client ({self.master_client}): {ret}")

results = []

Expand All @@ -357,6 +226,7 @@ def post(self, data):
except JSONRPCError as e:
self.logger.error(e)
results.append(e)
self.logger.debug(f"Result from client {client}: {results[-1]}")

if ret is None:
return None
Expand Down Expand Up @@ -487,6 +357,8 @@ def post(self):

ret = ETHENO.post(data)

ETHENO.logger.debug(f"Returning {ret}")

if ret is None:
return None

Expand Down
5 changes: 2 additions & 3 deletions etheno/ganache.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ def __init__(self, args=None, port=8546):
super().__init__("http://127.0.0.1:%d/" % port)
self.port = port
if args is None:
self.args = []
else:
self.args = ['/usr/bin/env', 'ganache-cli', '-d', '-p', str(port)] + args
args = []
self.args = ['/usr/bin/env', 'ganache-cli', '-d', '-p', str(port)] + args
self.ganache = None
self._client = None
def start(self):
Expand Down
38 changes: 34 additions & 4 deletions etheno/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,47 @@ def format(self, *args, **kwargs):
else:
return self._parent_formatter.format(*args, **kwargs)

ETHENO_LOGGERS = {}

_LOGGING_GETLOGGER = logging.getLogger
def getLogger(name):
if name in ETHENO_LOGGERS:
# TODO: Only enable this if Etheno was run as a standalone application
ret = ETHENO_LOGGERS[name]
else:
ret = _LOGGING_GETLOGGER(name)
# ####BEGIN####
# Horrible hack to workaround Manticore's global logging system.
# This can be removed after https://github.com/trailofbits/manticore/issues/1369
# is resolved.
if name.startswith('manticore'):
ret.propagate = False
# ####END####
return ret
logging.getLogger = getLogger

class EthenoLogger(object):
DEFAULT_FORMAT='$RESET$LEVELCOLOR$BOLD%(levelname)-8s $BLUE[$RESET$WHITE%(asctime)14s$BLUE$BOLD]$NAME$RESET %(message)s'

def __init__(self, name, log_level=None, parent=None, cleanup_empty=False):
def __init__(self, name, log_level=None, parent=None, cleanup_empty=False, displayname=None):
if name in ETHENO_LOGGERS:
raise Exception(f'An EthenoLogger instance for name {name} already exists: {ETHENO_LOGGERS[name]}')
ETHENO_LOGGERS[name] = self
self._directory = None
self.parent = parent
self.cleanup_empty = cleanup_empty
self.children = []
self._descendant_handlers = []
if displayname is None:
self.displayname = name
else:
self.displayname = displayname
if log_level is None:
if parent is None:
raise ValueError('A logger must be provided a parent if `log_level` is None')
log_level = parent.log_level
self._log_level = log_level
self._logger = logging.getLogger(name)
self._logger = _LOGGING_GETLOGGER(name)
self._handlers = [logging.StreamHandler()]
if log_level is not None:
self.log_level = log_level
Expand Down Expand Up @@ -136,7 +162,7 @@ def directory(self):
return self._directory

def _add_child(self, child):
if child in self.children:
if child in self.children or any(c for c in self.children if c.name == child.name):
raise ValueError("Cannot double-add child logger %s to logger %s" % (child.name, self.name))
self.children.append(child)
if self.directory is not None:
Expand All @@ -155,7 +181,7 @@ def _name_format(self):
ret = self.parent._name_format()
else:
ret = ''
return ret + "[$RESET$WHITE%s$BLUE$BOLD]" % self._logger.name
return ret + "[$RESET$WHITE%s$BLUE$BOLD]" % self.displayname

def addHandler(self, handler, include_descendants=True, set_log_level=True):
if set_log_level:
Expand Down Expand Up @@ -249,6 +275,10 @@ def log_level(self, level):
def __getattr__(self, name):
return getattr(self._logger, name)

def __repr__(self):
return f'{type(self).__name__}(name={self.name!r}, log_level={self.log_level!r}, parent={self.parent!r}, cleanup_empty={self.cleanup_empty!r}, displayname={self.displayname!r})'


class StreamLogger(threading.Thread):
def __init__(self, logger, *streams, newline_char=b'\n'):
super().__init__(daemon=True)
Expand Down
Loading