Repository: angr/angr

Issue:
`SimZeroDivisionException` when running `CFGFast()`
On an httpd binary, I try to get the CFG using `CFGFast()`, but VEX raises an error.

```python
import angr
project = angr.Project('binaries/httpd', auto_load_libs=False)
cfg = project.analyses.CFGFast()
[...]
SimZeroDivisionException: divide by zero!
```

<details>
  <summary>The full trace</summary>

```
ClaripyZeroDivisionError                  Traceback (most recent call last)
~/Workspace/angr/angr/angr/engines/vex/irop.py in calculate(self, *args)
    371         try:
--> 372             return self.extend_size(self._calculate(args))
    373         except (ZeroDivisionError, claripy.ClaripyZeroDivisionError) as e:

~/Workspace/angr/angr/angr/engines/vex/irop.py in _op_divmod(self, args)
    717         if self.is_signed:
--> 718             quotient = (args[0].SDiv(claripy.SignExt(self._from_size - self._to_size, args[1])))
    719             remainder = (args[0].SMod(claripy.SignExt(self._from_size - self._to_size, args[1])))

~/Workspace/angr/claripy/claripy/operations.py in _op(*args)
     66
---> 67         return return_type(name, fixed_args, **kwargs)
     68

~/Workspace/angr/claripy/claripy/ast/base.py in __new__(cls, op, args, add_variables, hash, **kwargs)
    152                 try:
--> 153                     r = operations._handle_annotations(eb._abstract(eb.call(op, args)), args)
    154                     if r is not None:

~/Workspace/angr/claripy/claripy/backends/__init__.py in call(self, op, args)
    238         converted = self.convert_list(args)
--> 239         return self._call(op, converted)
    240

~/Workspace/angr/claripy/claripy/backends/__init__.py in _call(self, op, args)
    249             # the raw ops don't get the model, cause, for example, Z3 stuff can't take it
--> 250             obj = self._op_raw[op](*args)
    251         elif not op.startswith("__"):

~/Workspace/angr/claripy/claripy/bv.py in normalize_helper(self, o)
     41             return NotImplemented
---> 42         return f(self, o)
     43

~/Workspace/angr/claripy/claripy/bv.py in compare_guard(self, o)
     15             raise ClaripyTypeError("bitvectors are differently-sized (%d and %d)" % (self.bits, o.bits))
---> 16         return f(self, o)
     17

~/Workspace/angr/claripy/claripy/bv.py in SDiv(self, o)
    417     if b == 0:
--> 418         raise ClaripyZeroDivisionError()
    419     val = a//b if a*b>0 else (a+(-a%b))//b

ClaripyZeroDivisionError:

The above exception was the direct cause of the following exception:

SimZeroDivisionException                  Traceback (most recent call last)
<ipython-input-6-96f129e3fa7c> in <module>
----> 1 cfg = project.analyses.CFGFast()

~/Workspace/angr/angr/angr/analyses/analysis.py in __call__(self, *args, **kwargs)
    107
    108         oself._show_progressbar = show_progressbar
--> 109         oself.__init__(*args, **kwargs)
    110         return oself
    111

~/Workspace/angr/angr/angr/analyses/cfg/cfg_fast.py in __init__(self, binary, objects, regions, pickle_intermediate_results, symbols, function_prologues, resolve_indirect_jumps, force_segment, force_complete_scan, indirect_jump_target_limit, data_references, cross_references, normalize, start_at_entry, function_starts, extra_memory_regions, data_type_guessing_handlers, arch_options, indirect_jump_resolvers, base_state, exclude_sparse_regions, skip_specific_regions, heuristic_plt_resolving, detect_tail_calls, low_priority, cfb, model, use_patches, start, end, collect_data_references, extra_cross_references, **extra_arch_options)
    643
    644         # Start working!
--> 645         self._analyze()
    646
    647     def __getstate__(self):

~/Workspace/angr/angr/angr/analyses/forward_analysis.py in _analyze(self)
    581             # An example is the CFG recovery.
    582
--> 583             self._analysis_core_baremetal()
    584
    585         else:

~/Workspace/angr/angr/angr/analyses/forward_analysis.py in _analysis_core_baremetal(self)
    706             self._job_info_queue = self._job_info_queue[1:]
    707
--> 708             self._process_job_and_get_successors(job_info)
    709
    710             # Short-cut for aborting the analysis

~/Workspace/angr/angr/angr/analyses/forward_analysis.py in _process_job_and_get_successors(self, job_info)
    724         job = job_info.job
    725
--> 726         successors = self._get_successors(job)
    727
    728         all_new_jobs = [ ]

~/Workspace/angr/angr/angr/analyses/cfg/cfg_fast.py in _get_successors(self, job)
   1050         #    l.debug("Tracing new exit %#x", addr)
   1051
-> 1052         jobs = self._scan_block(job)
   1053
   1054         # l.debug("... got %d jobs: %s", len(jobs), jobs)

~/Workspace/angr/angr/angr/analyses/cfg/cfg_fast.py in _scan_block(self, cfg_job)
   1351
   1352         else:
-> 1353             entries = self._scan_irsb(cfg_job, current_func_addr)
   1354
   1355         return entries

~/Workspace/angr/angr/angr/analyses/cfg/cfg_fast.py in _scan_irsb(self, cfg_job, current_func_addr)
   1544
   1545             entries += self._create_jobs(target, jumpkind, function_addr, irsb, addr, cfg_node, ins_addr,
-> 1546                                          stmt_idx
   1547                                          )
   1548

~/Workspace/angr/angr/angr/analyses/cfg/cfg_fast.py in _create_jobs(self, target, jumpkind, current_function_addr, irsb, addr, cfg_node, ins_addr, stmt_idx)
   1712             elif jumpkind == 'Ijk_Call' or jumpkind.startswith("Ijk_Sys"):
   1713                 jobs += self._create_job_call(addr, irsb, cfg_node, stmt_idx, ins_addr, current_function_addr,
-> 1714                                               target_addr, jumpkind, is_syscall=is_syscall
   1715                                               )
   1716

~/Workspace/angr/angr/angr/analyses/cfg/cfg_fast.py in _create_job_call(self, addr, irsb, cfg_node, stmt_idx, ins_addr, current_function_addr, target_addr, jumpkind, is_syscall)
   1747             tmp_state = self.project.factory.blank_state(mode="fastpath", addr=cfg_node.addr)
   1748             # Find the first successor with a syscall jumpkind
-> 1749             succ = next(iter(succ for succ in self.project.factory.successors(tmp_state).flat_successors
   1750                              if succ.history.jumpkind and succ.history.jumpkind.startswith("Ijk_Sys")), None)
   1751             if succ is None:

~/Workspace/angr/angr/angr/factory.py in successors(self, *args, **kwargs)
     52         """
     53
---> 54         return self.project.engines.successors(*args, **kwargs)
     55
     56     def blank_state(self, **kwargs):

~/Workspace/angr/angr/angr/engines/hub.py in successors(self, state, addr, jumpkind, default_engine, procedure_engine, engines, **kwargs)
    126         for engine in engines:
    127             if engine.check(state, **kwargs):
--> 128                 r = engine.process(state, **kwargs)
    129                 if r.processed:
    130                     return r

~/Workspace/angr/angr/angr/engines/vex/engine.py in process(self, state, irsb, skip_stmts, last_stmt, whitelist, inline, force_addr, insn_bytes, size, num_inst, traceflags, thumb, extra_stop_points, opt_level, **kwargs)
    146                 thumb=thumb,
    147                 extra_stop_points=extra_stop_points,
--> 148                 opt_level=opt_level)
    149
    150     def _check(self, state, *args, **kwargs):

~/Workspace/angr/angr/angr/engines/engine.py in process(***failed resolving arguments***)
     58         successors = new_state._inspect_getattr('sim_successors', successors)
     59         try:
---> 60             self._process(new_state, successors, *args, **kwargs)
     61         except SimException:
     62             if o.EXCEPTION_HANDLING not in old_state.options:

~/Workspace/angr/angr/angr/engines/vex/engine.py in _process(self, state, successors, irsb, skip_stmts, last_stmt, whitelist, insn_bytes, size, num_inst, traceflags, thumb, extra_stop_points, opt_level)
    197
    198             try:
--> 199                 self._handle_irsb(state, successors, irsb, skip_stmts, last_stmt, whitelist)
    200             except SimReliftException as e:
    201                 state = e.state

~/Workspace/angr/angr/angr/engines/vex/engine.py in _handle_irsb(self, state, successors, irsb, skip_stmts, last_stmt, whitelist)
    276                 state.scratch.stmt_idx = stmt_idx
    277                 state._inspect('statement', BP_BEFORE, statement=stmt_idx)
--> 278                 cont = self._handle_statement(state, successors, stmt)
    279                 state._inspect('statement', BP_AFTER)
    280                 if not cont:

~/Workspace/angr/angr/angr/engines/vex/engine.py in _handle_statement(self, state, successors, stmt)
    391             return None
    392         else:
--> 393             exit_data = stmt_handler(self, state, stmt)
    394
    395         # for the exits, put *not* taking the exit on the list of constraints so

~/Workspace/angr/angr/angr/engines/vex/statements/wrtmp.py in SimIRStmt_WrTmp(engine, state, stmt)
      2     # get data and track data reads
      3     with state.history.subscribe_actions() as data_deps:
----> 4         data = engine.handle_expression(state, stmt.data)
      5     state.scratch.store_tmp(stmt.tmp, data, deps=data_deps)
      6

~/Workspace/angr/angr/angr/engines/vex/engine.py in handle_expression(self, state, expr)
    452
    453         state._inspect('expr', BP_BEFORE, expr=expr)
--> 454         result = handler(self, state, expr)
    455
    456         if o.SIMPLIFY_EXPRS in state.options:

~/Workspace/angr/angr/angr/engines/vex/expressions/op.py in SimIRExpr_Op(engine, state, expr)
     12
     13     try:
---> 14         result = translate(state, expr.op, exprs)
     15
     16         if o.TRACK_OP_ACTIONS in state.options:

~/Workspace/angr/angr/angr/engines/vex/irop.py in translate(state, op, s_args)
   1042         l.error(error)
   1043         raise UnsupportedIROpError(error)
-> 1044     return translate_inner(state, simop, s_args)
   1045
   1046

~/Workspace/angr/angr/angr/engines/vex/irop.py in translate_inner(state, irop, s_args)
   1049         if irop._float and not options.SUPPORT_FLOATING_POINT in state.options:
   1050             raise UnsupportedIROpError("floating point support disabled")
-> 1051         return irop.calculate(*s_args)
   1052     except SimZeroDivisionException:
   1053         if state.mode == 'static' and len(s_args) == 2 and state.solver.is_true(s_args[1] == 0):

~/Workspace/angr/angr/angr/engines/vex/irop.py in calculate(self, *args)
    372             return self.extend_size(self._calculate(args))
    373         except (ZeroDivisionError, claripy.ClaripyZeroDivisionError) as e:
--> 374             raise SimZeroDivisionException("divide by zero!") from e
    375         except (TypeError, ValueError, SimValueError, claripy.ClaripyError) as e:
    376             raise SimOperationError("%s._calculate() raised exception" % self.name) from e

SimZeroDivisionException: divide by zero!
```
</details>


Discussion:
Finally, I was able to upload the relevant binary!
[httpd.zip](https://github.com/angr/angr/files/3571059/httpd.zip)
Awesome. Now it's on my TODO list.

Resolving patch:
diff --git a/angr/analyses/cfg/cfg_fast.py b/angr/analyses/cfg/cfg_fast.py
--- a/angr/analyses/cfg/cfg_fast.py
+++ b/angr/analyses/cfg/cfg_fast.py
@@ -19,7 +19,7 @@
 from ...misc.ux import deprecated
 from ... import sim_options as o
 from ...errors import (AngrCFGError, SimEngineError, SimMemoryError, SimTranslationError, SimValueError,
-                       AngrUnsupportedSyscallError
+                       SimOperationError, SimError, AngrUnsupportedSyscallError
                        )
 from ...utils.constants import DEFAULT_STATEMENT
 from ..forward_analysis import ForwardAnalysis, AngrSkipJobNotice
@@ -1737,15 +1737,19 @@ def _create_job_call(self, addr, irsb, cfg_node, stmt_idx, ins_addr, current_fun
             # Fix the target_addr for syscalls
             tmp_state = self.project.factory.blank_state(mode="fastpath", addr=cfg_node.addr)
             # Find the first successor with a syscall jumpkind
-            succ = next(iter(succ for succ in self.project.factory.successors(tmp_state).flat_successors
-                             if succ.history.jumpkind and succ.history.jumpkind.startswith("Ijk_Sys")), None)
+            successors = self._simulate_block_with_resilience(tmp_state)
+            if successors is not None:
+                succ = next(iter(succ for succ in successors.flat_successors
+                                 if succ.history.jumpkind and succ.history.jumpkind.startswith("Ijk_Sys")), None)
+            else:
+                succ = None
             if succ is None:
                 # For some reason, there is no such successor with a syscall jumpkind
                 target_addr = self._unresolvable_call_target_addr
             else:
                 try:
                     syscall_stub = self.project.simos.syscall(succ)
-                    if syscall_stub:  # can be None if simos is not a subclass of SimUserspac
+                    if syscall_stub:  # can be None if simos is not a subclass of SimUserspace
                         syscall_addr = syscall_stub.addr
                         target_addr = syscall_addr
                     else:
@@ -1838,6 +1842,30 @@ def _create_job_call(self, addr, irsb, cfg_node, stmt_idx, ins_addr, current_fun
 
         return jobs
 
+    def _simulate_block_with_resilience(self, state):
+        """
+        Execute a basic block with "On Error Resume Next". Give up when there is no way moving forward.
+
+        :param SimState state:  The initial state to start simulation with.
+        :return:                A SimSuccessors instance or None if we are unable to resume execution with resilience.
+        :rtype:                 SimSuccessors or None
+        """
+
+        stmt_idx = 0
+        successors = None  # make PyCharm's linting happy
+
+        while True:
+            try:
+                successors = self.project.factory.successors(state, skip_stmts=stmt_idx)
+                break
+            except SimOperationError as ex:
+                stmt_idx = ex.stmt_idx + 1
+                continue
+            except SimError:
+                return None
+
+        return successors
+
     # Data reference processing
 
     def _collect_data_references(self, irsb, irsb_addr):


Test patch: