Repository: angr/angr

Issue:
VariableRecoveryFast fails miserably on CGC binaries
**Describe the bug**
When trying to do `VariableRecovery` analysis on a cgc binary, angr breaks.
And the error message is like this
~~~
Traceback (most recent call last):
  File "/home/kylebot/.virtualenvs/angr/lib/python3.6/site-packages/networkx/classes/digraph.py", line 794, in successors
    return iter(self._succ[n])
KeyError: None

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "script.py", line 5, in <module>
    proj.analyses.VariableRecoveryFast(proj.kb.functions[0x080481c0])
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/analysis.py", line 109, in __call__
    oself.__init__(*args, **kwargs)
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/variable_recovery/variable_recovery_fast.py", line 524, in __init__
    self._analyze()
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/forward_analysis.py", line 545, in _analyze
    self._pre_analysis()
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/variable_recovery/variable_recovery_fast.py", line 532, in _pre_analysis
    self.initialize_dominance_frontiers()
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/variable_recovery/variable_recovery_base.py", line 78, in initialize_dominance_frontiers
    df = self.project.analyses.DominanceFrontier(self.function)
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/analysis.py", line 109, in __call__
    oself.__init__(*args, **kwargs)
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/dominance_frontier.py", line 20, in __init__
    self._compute()
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/dominance_frontier.py", line 32, in _compute
    doms = Dominators(g, self.function.startpoint)
  File "/home/kylebot/src/angr-dev/angr/angr/utils/graph.py", line 184, in __init__
    self._construct(graph, entry_node)
  File "/home/kylebot/src/angr-dev/angr/angr/utils/graph.py", line 214, in _construct
    _prepared_graph, vertices, parent = self._prepare_graph(graph, entry_node)
  File "/home/kylebot/src/angr-dev/angr/angr/utils/graph.py", line 286, in _prepare_graph
    successors = list(self._graph_successors(graph, node))
  File "/home/kylebot/src/angr-dev/angr/angr/utils/graph.py", line 201, in _graph_successors
    return graph.successors(node)
  File "/home/kylebot/.virtualenvs/angr/lib/python3.6/site-packages/networkx/classes/digraph.py", line 796, in successors
    raise NetworkXError("The node %s is not in the digraph." % (n,))
networkx.exception.NetworkXError: The node None is not in the digraph.
~~~

**To Reproduce**
all in https://gist.github.com/Kyle-Kyle/77a6b66adda24dfdc2ad006e1b5a2470




Discussion:
Function 0x80481c0 is empty. I'm looking into why.
Umm there is no function 0x80481c0... I'm going to fix the resiliency problem so nothing crashes if the function is empty, but you should be checking if the function exists or not before getting one and passing it in to VariableRecoveryFast.

Resolving patch:
diff --git a/angr/analyses/dominance_frontier.py b/angr/analyses/dominance_frontier.py
--- a/angr/analyses/dominance_frontier.py
+++ b/angr/analyses/dominance_frontier.py
@@ -29,6 +29,9 @@ def _compute(self):
         g = self._get_graph()
 
         # Compute the dominator tree
+        if self.function.startpoint is None:
+            # The function might be empty or is corrupted (maybe the object is created manually)
+            raise TypeError("Startpoint of function %s is None. Is this function empty?" % repr(self.function))
         doms = Dominators(g, self.function.startpoint)
 
         # Compute the dominance frontier
diff --git a/angr/analyses/variable_recovery/variable_recovery_fast.py b/angr/analyses/variable_recovery/variable_recovery_fast.py
--- a/angr/analyses/variable_recovery/variable_recovery_fast.py
+++ b/angr/analyses/variable_recovery/variable_recovery_fast.py
@@ -5,7 +5,7 @@
 import ailment
 
 from ...engines.light import SpOffset, SimEngineLightVEX, SimEngineLightAIL
-from ...errors import SimEngineError
+from ...errors import SimEngineError, AngrVariableRecoveryError
 from ...knowledge_plugins import Function
 from ...sim_variable import SimStackVariable, SimRegisterVariable
 from ..calling_convention import CallingConventionAnalysis
@@ -508,6 +508,10 @@ def __init__(self, func, max_iterations=3, clinic=None):
 
         function_graph_visitor = FunctionGraphVisitor(func)
 
+        # Make sure the function is not empty
+        if not func.block_addrs_set or func.startpoint is None:
+            raise AngrVariableRecoveryError("Function %s is empty." % repr(func))
+
         VariableRecoveryBase.__init__(self, func, max_iterations)
         ForwardAnalysis.__init__(self, order_jobs=True, allow_merging=True, allow_widening=False,
                                  graph_visitor=function_graph_visitor)
diff --git a/angr/errors.py b/angr/errors.py
--- a/angr/errors.py
+++ b/angr/errors.py
@@ -125,6 +125,14 @@ class AngrDirectorError(AngrExplorationTechniqueError):
 class AngrTracerError(AngrExplorationTechniqueError):
     pass
 
+
+#
+# VariableRecovery errors
+#
+
+class AngrVariableRecoveryError(AngrAnalysisError):
+    pass
+
 #
 # Tracer
 #


Test patch:
diff --git a/tests/test_variablerecovery.py b/tests/test_variablerecovery.py
--- a/tests/test_variablerecovery.py
+++ b/tests/test_variablerecovery.py
@@ -1,4 +1,5 @@
 
+import sys
 import os
 import logging
 
@@ -127,11 +128,11 @@ def run_variable_recovery_analysis(project, func, groundtruth, is_fast):
             l.debug("Found phi variable %s at %#x.", the_var, block_addr)
 
 
-def test_variable_analysis():
+def test_variable_recovery_fauxware():
 
     binary_path = os.path.join(test_location, 'x86_64', 'fauxware')
     project = angr.Project(binary_path, load_options={'auto_load_libs': False})
-    cfg = project.analyses.CFG()
+    cfg = project.analyses.CFG(normalize=True)
 
     groundtruth = {
         'authenticate': {
@@ -205,12 +206,20 @@ def test_variable_analysis():
 def main():
 
     g = globals()
-    for func_name, func in g.items():
-        if func_name.startswith('test_') and hasattr(func, '__call__'):
-            print(func_name)
-            for testfunc_and_args in func():
-                testfunc, args = testfunc_and_args[0], testfunc_and_args[1:]
-                testfunc(*args)
+    if len(sys.argv) > 1:
+        func_name = "test_%s" % sys.argv[1]
+        if func_name not in g:
+            func_name = "test_variable_recovery_%s" % sys.argv[1]
+        for testfunc_and_args in g[func_name]():
+            testfunc, args = testfunc_and_args[0], testfunc_and_args[1:]
+            testfunc(*args)
+    else:
+        for func_name, func in g.items():
+            if func_name.startswith('test_') and hasattr(func, '__call__'):
+                print(func_name)
+                for testfunc_and_args in func():
+                    testfunc, args = testfunc_and_args[0], testfunc_and_args[1:]
+                    testfunc(*args)
 
 
 if __name__ == '__main__':