def _convert_to_id(d): """ Id keeps the source mapping of the node, otherwise we risk to consider two different node as the same :param d: :return: """ if isinstance(d, str): return d if not isinstance(d, SourceMapping): raise FortressError(f"{d} does not inherit from SourceMapping, conversion impossible") if isinstance(d, Node): if d.expression: return f"{d.expression} ({d.source_mapping_str})" return f"{str(d)} ({d.source_mapping_str})" if isinstance(d, Pragma): return f"{d} ({d.source_mapping_str})" if hasattr(d, "canonical_name"): return f"{d.canonical_name}" if hasattr(d, "name"): return f"{d.name}" raise FortressError(f"{type(d)} cannot be converted (no name, or canonical_name")
def _get_evm_instructions_function(function_info): function = function_info["function"] # CFG depends on function being constructor or not if function.is_constructor: cfg = function_info["contract_info"]["cfg_init"] # _dispatcher is the only function recognised by evm-cfg-builder in bytecode_init. # _dispatcher serves the role of the constructor in init code, # given that there are no other functions. # Todo: Could rename it appropriately in evm-cfg-builder # by detecting that init bytecode is being parsed. name = "_dispatcher" func_hash = "" else: cfg = function_info["contract_info"]["cfg"] name = function.name # Get first four bytes of function singature's keccak-256 hash used as function selector func_hash = str(hex(get_function_id(function.full_name))) function_evm = _get_function_evm(cfg, name, func_hash) if function_evm is None: to_log = "Function " + function.name + " not found in the EVM code" logger.error(to_log) raise FortressError("Function " + function.name + " not found in the EVM code") function_ins = [] for basic_block in sorted(function_evm.basic_blocks, key=lambda x: x.start.pc): for ins in basic_block.instructions: function_ins.append(ins) return function_ins
def _init_from_raw_json(self, filename): if not os.path.isfile(filename): raise FortressError( "{} does not exist (are you in the correct directory?)".format( filename)) assert filename.endswith("json") with open(filename, encoding="utf8") as astFile: stdout = astFile.read() if not stdout: to_log = f"Empty AST file: {filename}" raise FortressError(to_log) contracts_json = stdout.split("\n=") self._parser = FortressSolc(filename, self) for c in contracts_json: self._parser.parse_contracts_from_json(c)
def _visit_expression(self, expression: Expression): # pylint: disable=too-many-branches self._pre_visit(expression) if isinstance(expression, AssignmentOperation): self._visit_assignement_operation(expression) elif isinstance(expression, BinaryOperation): self._visit_binary_operation(expression) elif isinstance(expression, CallExpression): self._visit_call_expression(expression) elif isinstance(expression, ConditionalExpression): self._visit_conditional_expression(expression) elif isinstance(expression, ElementaryTypeNameExpression): self._visit_elementary_type_name_expression(expression) elif isinstance(expression, Identifier): self._visit_identifier(expression) elif isinstance(expression, IndexAccess): self._visit_index_access(expression) elif isinstance(expression, Literal): self._visit_literal(expression) elif isinstance(expression, MemberAccess): self._visit_member_access(expression) elif isinstance(expression, NewArray): self._visit_new_array(expression) elif isinstance(expression, NewContract): self._visit_new_contract(expression) elif isinstance(expression, NewElementaryType): self._visit_new_elementary_type(expression) elif isinstance(expression, TupleExpression): self._visit_tuple_expression(expression) elif isinstance(expression, TypeConversion): self._visit_type_conversion(expression) elif isinstance(expression, UnaryOperation): self._visit_unary_operation(expression) elif expression is None: pass else: raise FortressError( "Expression not handled: {}".format(expression)) self._post_visit(expression)
def _convert_to_description(d): if isinstance(d, str): return d if not isinstance(d, SourceMapping): raise FortressError(f"{d} does not inherit from SourceMapping, conversion impossible") if isinstance(d, Node): if d.expression: return f"{d.expression} ({d.source_mapping_str})" return f"{str(d)} ({d.source_mapping_str})" if hasattr(d, "canonical_name"): return f"{d.canonical_name} ({d.source_mapping_str})" if hasattr(d, "name"): return f"{d.name} ({d.source_mapping_str})" raise FortressError(f"{type(d)} cannot be converted (no name, or canonical_name")
def _get_function_or_variable(contract, signature): f = contract.get_function_from_signature(signature) if f: return f for variable in contract.state_variables: # Todo: can lead to incorrect variable in case of shadowing if variable.visibility in ["public"]: if variable.name + "()" == signature: return variable raise FortressError(f"Function id checks: {signature} not found in {contract.name}")
def load_evm_cfg_builder(): try: # Avoiding the addition of evm_cfg_builder as permanent dependency # pylint: disable=import-outside-toplevel from evm_cfg_builder.cfg import CFG return CFG except ImportError: logger.error( "To use evm features, you need to install evm-cfg-builder") logger.error( "Documentation: https://github.com/crytic/evm_cfg_builder") logger.error("Installation: pip install evm-cfg-builder") # pylint: disable=raise-missing-from raise FortressError("evm-cfg-builder not installed.")
def add(self, add: SupportedOutput, additional_fields: Optional[Dict] = None): if isinstance(add, Variable): self.add_variable(add, additional_fields=additional_fields) elif isinstance(add, Contract): self.add_contract(add, additional_fields=additional_fields) elif isinstance(add, Function): self.add_function(add, additional_fields=additional_fields) elif isinstance(add, Enum): self.add_enum(add, additional_fields=additional_fields) elif isinstance(add, Event): self.add_event(add, additional_fields=additional_fields) elif isinstance(add, Structure): self.add_struct(add, additional_fields=additional_fields) elif isinstance(add, Pragma): self.add_pragma(add, additional_fields=additional_fields) elif isinstance(add, Node): self.add_node(add, additional_fields=additional_fields) else: raise FortressError(f"Impossible to add {type(add)} to the json")
def get_evm_instructions(obj): assert isinstance(obj, (Function, Contract, Node)) if KEY_EVM_INS not in obj.context: CFG = load_evm_cfg_builder() fortress = obj.fortress if not fortress.crytic_compile: raise FortressError("EVM features require to compile with crytic-compile") contract_info = {} function_info = {} node_info = {} if isinstance(obj, Node): contract_info["contract"] = obj.function.contract elif isinstance(obj, Function): contract_info["contract"] = obj.contract else: contract_info["contract"] = obj # Get contract runtime bytecode, srcmap and cfg contract_info["bytecode_runtime"] = fortress.crytic_compile.bytecode_runtime( contract_info["contract"].name ) contract_info["srcmap_runtime"] = fortress.crytic_compile.srcmap_runtime( contract_info["contract"].name ) contract_info["cfg"] = CFG(contract_info["bytecode_runtime"]) # Get contract init bytecode, srcmap and cfg contract_info["bytecode_init"] = fortress.crytic_compile.bytecode_init( contract_info["contract"].name ) contract_info["srcmap_init"] = fortress.crytic_compile.srcmap_init( contract_info["contract"].name ) contract_info["cfg_init"] = CFG(contract_info["bytecode_init"]) # Get evm instructions if isinstance(obj, Contract): # Get evm instructions for contract obj.context[KEY_EVM_INS] = _get_evm_instructions_contract(contract_info) elif isinstance(obj, Function): # Get evm instructions for function function_info["function"] = obj function_info["contract_info"] = contract_info obj.context[KEY_EVM_INS] = _get_evm_instructions_function(function_info) else: # Get evm instructions for node node_info["node"] = obj # CFG and srcmap depend on function being constructor or not if node_info["node"].function.is_constructor: cfg = contract_info["cfg_init"] srcmap = contract_info["srcmap_init"] else: cfg = contract_info["cfg"] srcmap = contract_info["srcmap_runtime"] node_info["cfg"] = cfg node_info["srcmap"] = srcmap node_info["contract"] = contract_info["contract"] node_info["fortress"] = fortress obj.context[KEY_EVM_INS] = _get_evm_instructions_node(node_info) return obj.context.get(KEY_EVM_INS, [])
def __init__(self, target, **kwargs): """ Args: target (str | list(json) | CryticCompile) Keyword Args: solc (str): solc binary location (default 'solc') disable_solc_warnings (bool): True to disable solc warnings (default false) solc_arguments (str): solc arguments (default '') ast_format (str): ast format (default '--ast-compact-json') filter_paths (list(str)): list of path to filter (default []) triage_mode (bool): if true, switch to triage mode (default false) exclude_dependencies (bool): if true, exclude results that are only related to dependencies generate_patches (bool): if true, patches are generated (json output only) truffle_ignore (bool): ignore truffle.js presence (default false) truffle_build_directory (str): build truffle directory (default 'build/contracts') truffle_ignore_compile (bool): do not run truffle compile (default False) truffle_version (str): use a specific truffle version (default None) embark_ignore (bool): ignore embark.js presence (default false) embark_ignore_compile (bool): do not run embark build (default False) embark_overwrite_config (bool): overwrite original config file (default false) """ super().__init__() self._parser: FortressSolc # This could be another parser, like FortressVyper, interface needs to be determined self._disallow_partial: bool = kwargs.get("disallow_partial", False) self._skip_assembly: bool = kwargs.get("skip_assembly", False) # list of files provided (see --splitted option) if isinstance(target, list): self._init_from_list(target) elif isinstance(target, str) and target.endswith(".json"): self._init_from_raw_json(target) else: self._parser = FortressSolc("", self) try: if isinstance(target, CryticCompile): crytic_compile = target else: crytic_compile = CryticCompile(target, **kwargs) self._crytic_compile = crytic_compile except InvalidCompilation as e: # pylint: disable=raise-missing-from raise FortressError(f"Invalid compilation: \n{str(e)}") for path, ast in crytic_compile.asts.items(): self._parser.parse_contracts_from_loaded_json(ast, path) self.add_source_code(path) if kwargs.get("generate_patches", False): self.generate_patches = True self._markdown_root = kwargs.get("markdown_root", "") self._detectors = [] self._printers = [] filter_paths = kwargs.get("filter_paths", []) for p in filter_paths: self.add_path_to_filter(p) self._exclude_dependencies = kwargs.get("exclude_dependencies", False) triage_mode = kwargs.get("triage_mode", False) self._triage_mode = triage_mode self._parser.parse_contracts() # skip_analyze is only used for testing if not kwargs.get("skip_analyze", False): self._parser.analyze_contracts()