Repository: angr/angr

Issue:
A bug of CFGFast while disassembling an instruction jumping to a table.
<!--
*Disclaimer:
The angr suite is maintained by a small team of volunteers.
While we cannot guarantee any timeliness for fixes and enhancements, we will do our best.
For more real-time help with angr, from us and the community, join our [Slack.](http://angr.io/invite/)*
-->
---

**Describe the bug.**
<!--
Please include a clear and concise description of what the bug is.
-->
It seems Angr has some bugs while disassembling an instruction jumping to a table.

**Environment Information.**
<!--
Many common issues are caused by problems with the local Python environment.
Before submitting, double-check that your versions of all modules in the angr suite (angr, cle, pyvex, ...) are up to date.
Please include the output of `python -m angr.misc.bug_report` here.
-->
angr environment report
```
=============================
Date: 2019-07-17 14:53:18.016625
Running in virtual environment at E:\2019_summer\angr-dev\env
Platform: win-amd64
Python version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
######## angr #########
Python found it in e:\2019_summer\angr-dev\angr\angr
Pip version angr 8.19.4.5
Git info:
        Current commit 48a7297f70f9420f3a79882b48bf9a682f910a0c from branch master
        Checked out from remote origin: https://github.com/angr/angr.git
######## ailment #########
Python found it in e:\2019_summer\angr-dev\ailment\ailment
Pip version ailment 8.19.4.5
Git info:
        Current commit ba79ef85aaf5a12fe54c41841c1044ccbab28843 from branch master
        Checked out from remote origin: https://github.com/angr/ailment.git
######## cle #########
Python found it in e:\2019_summer\angr-dev\cle\cle
Pip version cle 8.19.4.5
Git info:
        Current commit e338ee784a19eaee7e266bba08b1f8caefc1abc4 from branch master
        Checked out from remote origin: https://github.com/angr/cle.git
######## pyvex #########
Python found it in e:\2019_summer\angr-dev\pyvex\pyvex
Pip version pyvex 8.19.4.5
Git info:
        Current commit ee27c7d01cd013b2efcc760d2568b19365a30ff2 from branch master
        Checked out from remote origin: https://github.com/angr/pyvex.git
######## claripy #########
Python found it in e:\2019_summer\angr-dev\claripy\claripy
Pip version claripy 8.19.4.5
Git info:
        Current commit c62e5b5f25a7b18c87ce4af22de56b818d45a05e from branch master
        Checked out from remote origin: https://github.com/angr/claripy.git
######## archinfo #########
Python found it in e:\2019_summer\angr-dev\archinfo\archinfo
Pip version archinfo 8.19.4.5
Git info:
        Current commit a37379f3fac00ebf532446ed8bb0c257011149b7 from branch master
        Checked out from remote origin: https://github.com/angr/archinfo.git
######## z3 #########
Python found it in E:\2019_summer\angr-dev\env\lib\site-packages\z3
Pip version not found!
Git info:
        Current commit 195dcfce5db1d7d7cd7d947a5eab5ba1dd649f5e from branch master
        Checked out from remote origin: https://github.com/angr/angr-dev
######## unicorn #########
Python found it in E:\2019_summer\angr-dev\env\lib\site-packages\unicorn
Pip version unicorn 1.0.1
Git info:
        Current commit 195dcfce5db1d7d7cd7d947a5eab5ba1dd649f5e from branch master
        Checked out from remote origin: https://github.com/angr/angr-dev
######### Native Module Info ##########
angr: <CDLL 'e:\2019_summer\angr-dev\angr\angr\lib\angr_native.dll', handle 7ffe1fab0000 at 0x2e41f7956d8>
unicorn: <CDLL 'E:\2019_summer\angr-dev\env\lib\site-packages\unicorn\lib\unicorn.dll', handle 6afc0000 at 0x2e41fbd0080>
pyvex: <cffi.api._make_ffi_library.<locals>.FFILibrary object at 0x000002E41F841C50>
z3: <CDLL 'E:\2019_summer\angr-dev\env\lib\site-packages\z3\lib\libz3.dll', handle 7ffdf4f10000 at 0x2e41f530ba8>
```

**To Reproduce.**
<!--
Please include *both a script to reproduce the crash, and attach the binary used, if possible*
-->
The PE is printenv.exe of coreutils for windows (http://gnuwin32.sourceforge.net/packages/coreutils.htm)

```
p = angr.Project('printenv.exe', auto_load_libs=False)
cfg = p.analyses.CFGFast()
cfg.jump_tables[0x402e4d] # we will see it is a jump table with 10 entries.
```
jump table entries are
```
[0x402e7c,
 0x402e81,
 0x402f21,
 0x402f2d,
 0x402f39,
 0x402f45,
 0x402f51,
 0x402f5d,
 0x402f69,
 0x402f75]
```
but the num of successors of cfg.mode.get_node(0x402e4d) is only 8.
```
[<CFGNode 0x402f21[12]>,
 <CFGNode 0x402f45[12]>,
 <CFGNode 0x402f69[12]>,
 <CFGNode 0x402f2d[12]>,
 <CFGNode 0x402f51[12]>,
 <CFGNode 0x402f75[12]>,
 <CFGNode 0x402f39[12]>,
 <CFGNode 0x402f5d[12]>]
```
and blocks at 0x402e7c and 0x402e81 are not a node of cfg

**Additional context.**
<!--
Add any other context about the problem here.
-->



Discussion:

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
@@ -2399,13 +2399,13 @@ def _indirect_jump_resolved(self, jump, jump_addr, resolved_by, targets):
         :return:                                    None
         """
 
-        from .indirect_jump_resolvers.jumptable import JumpTableResolver
-
         source_addr = jump.addr
 
-        if isinstance(resolved_by, JumpTableResolver):
+        if jump.jumptable:
             # Fill in the jump_tables dict
             self.jump_tables[jump.addr] = jump
+            # occupy the jump table region
+            self._seg_list.occupy(jump.jumptable_addr, jump.jumptable_size, "data")
 
         jump.resolved_targets = targets
         all_targets = set(targets)
diff --git a/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py b/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py
--- a/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py
+++ b/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py
@@ -574,7 +574,7 @@ def _resolve(self, cfg, addr, func_addr, b):
                     # Try the next state
                     continue
                 # unpack
-                jump_table, jump_table_addr, all_targets = ret
+                jump_table, jumptable_addr, entry_size, jumptable_size, all_targets = ret
                 l.info("Resolved %d targets from %#x.", len(all_targets), addr)
 
                 # write to the IndirectJump object in CFG
@@ -582,7 +582,9 @@ def _resolve(self, cfg, addr, func_addr, b):
                 if len(all_targets) > 1:
                     # It can be considered a jump table only if there are more than one jump target
                     ij.jumptable = True
-                    ij.jumptable_addr = jump_table_addr
+                    ij.jumptable_addr = jumptable_addr
+                    ij.jumptable_size = jumptable_size
+                    ij.jumptable_entry_size = entry_size
                     ij.resolved_targets = set(jump_table)
                     ij.jumptable_entries = jump_table
                 else:
@@ -996,7 +998,7 @@ def _try_resolve_targets(self, r, addr, cfg, annotatedcfg, load_stmt, load_size,
         if illegal_target_found:
             return None
 
-        return jump_table, min_jumptable_addr, all_targets
+        return jump_table, min_jumptable_addr, load_size, total_cases * load_size, all_targets
 
     @staticmethod
     def _instrument_statements(state, stmts_to_instrument, regs_to_initialize):
diff --git a/angr/knowledge_plugins/cfg/indirect_jump.py b/angr/knowledge_plugins/cfg/indirect_jump.py
--- a/angr/knowledge_plugins/cfg/indirect_jump.py
+++ b/angr/knowledge_plugins/cfg/indirect_jump.py
@@ -5,10 +5,10 @@
 class IndirectJump(Serializable):
 
     __slots__ = ("addr", "ins_addr", "func_addr", "jumpkind", "stmt_idx", "resolved_targets", "jumptable",
-                 "jumptable_addr", "jumptable_entries", )
+                 "jumptable_addr", "jumptable_size", "jumptable_entry_size", "jumptable_entries", )
 
     def __init__(self, addr, ins_addr, func_addr, jumpkind, stmt_idx, resolved_targets=None, jumptable=False,
-                 jumptable_addr=None, jumptable_entries=None):
+                 jumptable_addr=None, jumptable_size=None, jumptable_entry_size=None, jumptable_entries=None):
         self.addr = addr
         self.ins_addr = ins_addr
         self.func_addr = func_addr
@@ -17,6 +17,8 @@ def __init__(self, addr, ins_addr, func_addr, jumpkind, stmt_idx, resolved_targe
         self.resolved_targets = set() if resolved_targets is None else set(resolved_targets)
         self.jumptable = jumptable
         self.jumptable_addr = jumptable_addr
+        self.jumptable_size = jumptable_size
+        self.jumptable_entry_size = jumptable_entry_size
         self.jumptable_entries = jumptable_entries
 
     def __repr__(self):


Test patch:
diff --git a/tests/test_jumptables.py b/tests/test_jumptables.py
--- a/tests/test_jumptables.py
+++ b/tests/test_jumptables.py
@@ -180,6 +180,32 @@ def test_s390x_cfgswitches():
     compare(cfg.jump_tables, all_jumptables)
 
 
+#
+# The jump table should be occupied and marked as data
+#
+
+def test_jumptable_occupied_as_data():
+
+    # GitHub issue #1671
+
+    p = angr.Project(os.path.join(test_location, "i386", "windows", "printenv.exe"), auto_load_libs=False)
+    cfg = p.analyses.CFGFast()
+
+    # it has a jump table at 0x402e4d with 10 entries
+    nose.tools.assert_in(0x402e4d, cfg.indirect_jumps)
+    nose.tools.assert_true(cfg.indirect_jumps[0x402e4d].jumptable)
+    nose.tools.assert_equal(cfg.indirect_jumps[0x402e4d].jumptable_addr, 0x402e54)
+    nose.tools.assert_equal(cfg.indirect_jumps[0x402e4d].jumptable_size, 4 * 10)
+    nose.tools.assert_equal(cfg.indirect_jumps[0x402e4d].jumptable_entry_size, 4)
+
+    # 40 bytes starting at 0x402e4d should be marked as "data"
+    for addr in range(0x402e54, 0x402e54 + 40, 4):
+        nose.tools.assert_equal(cfg._seg_list.occupied_by_sort(addr), "data")
+
+    # node 0x402e4d should have 10 successors
+    nose.tools.assert_equal(len(cfg.model.get_any_node(0x402e4d).successors), 10)
+
+
 if __name__ == "__main__":
     test_amd64_dir_gcc_O0()
     test_amd64_cfgswitches_gcc()
@@ -189,3 +215,4 @@ def test_s390x_cfgswitches():
     test_kprca_00009()
     test_armel_cfgswitches_gcc()
     test_s390x_cfgswitches()
+    test_jumptable_occupied_as_data()