예제 #1
0
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
예제 #3
0
    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)
예제 #5
0
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}")
예제 #7
0
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.")
예제 #8
0
 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, [])
예제 #10
0
    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()