mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
113 lines
3.7 KiB
Python
113 lines
3.7 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
# pylint: disable=missing-class-docstring
|
||
|
|
"""Regression test for serialize/parse round-trip non-idempotence
|
||
|
|
when a Function has a fake_return edge whose destination is external
|
||
|
|
(in cmsg.external_blocks at save time) but whose edge attribute
|
||
|
|
outside=False.
|
||
|
|
|
||
|
|
Before the fix, parse_from_cmessage's call-edge handler trusted the
|
||
|
|
saved edge attribute and called _call_to(return_to_outside=False),
|
||
|
|
which made _call_to register the destination as a local block via
|
||
|
|
_register_node. Each roundtrip added one entry to
|
||
|
|
_local_block_addrs.
|
||
|
|
|
||
|
|
This shape arises organically when CFGFast calls
|
||
|
|
kb.functions._add_fakeret_to(..., confirmed=None) -- the underlying
|
||
|
|
Function._fakeret_to(confirmed=None) adds the edge with
|
||
|
|
outside=False but does NOT call _register_node for the to_node
|
||
|
|
(the `if confirmed:` branch is skipped). At save the dst goes to
|
||
|
|
cmsg.external_blocks; the edge keeps is_outside=False; the loader
|
||
|
|
then disagrees.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import tempfile
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
import angr
|
||
|
|
from angr.codenode import BlockNode, FuncNode
|
||
|
|
from angr.knowledge_plugins.functions.function import Function
|
||
|
|
|
||
|
|
|
||
|
|
class TestFunctionParserFakeret(unittest.TestCase):
|
||
|
|
def test_serialize_parse_roundtrip_with_external_fakeret(self):
|
||
|
|
blob = bytes.fromhex("ffc86b060000000000e86b060001")
|
||
|
|
addr = 0x100077547
|
||
|
|
fakeret_dst_addr = addr + 14
|
||
|
|
call_dst_addr = 0x101077BC0
|
||
|
|
|
||
|
|
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
|
||
|
|
f.write(blob)
|
||
|
|
blob_path = f.name
|
||
|
|
|
||
|
|
proj = angr.Project(
|
||
|
|
blob_path,
|
||
|
|
main_opts={
|
||
|
|
"backend": "blob",
|
||
|
|
"base_addr": addr,
|
||
|
|
"arch": "AMD64",
|
||
|
|
"entry_point": addr,
|
||
|
|
},
|
||
|
|
auto_load_libs=False,
|
||
|
|
)
|
||
|
|
|
||
|
|
fm = proj.kb.functions
|
||
|
|
func = fm.function(addr=addr, create=True)
|
||
|
|
assert func is not None
|
||
|
|
local_block = BlockNode(addr, 14, bytestr=blob[:14])
|
||
|
|
ext_block = BlockNode(fakeret_dst_addr, 4, bytestr=b"\x00" * 4)
|
||
|
|
|
||
|
|
func._register_node(True, local_block)
|
||
|
|
|
||
|
|
# Synthesize the call + fake_return edge pair that CFGFast can
|
||
|
|
# leave behind via _add_fakeret_to(confirmed=None).
|
||
|
|
call_target = FuncNode(call_dst_addr)
|
||
|
|
func.transition_graph.add_node(call_target)
|
||
|
|
func.transition_graph.add_edge(
|
||
|
|
local_block,
|
||
|
|
call_target,
|
||
|
|
type="call",
|
||
|
|
outside=False,
|
||
|
|
ins_addr=addr + 9,
|
||
|
|
stmt_idx=None,
|
||
|
|
)
|
||
|
|
func.transition_graph.add_node(ext_block)
|
||
|
|
func.transition_graph.add_edge(
|
||
|
|
local_block,
|
||
|
|
ext_block,
|
||
|
|
type="fake_return",
|
||
|
|
outside=False,
|
||
|
|
confirmed=True,
|
||
|
|
ins_addr=addr + 9,
|
||
|
|
stmt_idx=None,
|
||
|
|
)
|
||
|
|
|
||
|
|
pre = set(func._local_block_addrs)
|
||
|
|
cmsg = func.serialize_to_cmessage()
|
||
|
|
|
||
|
|
# Sanity: cmsg shape that exposes the bug.
|
||
|
|
self.assertEqual([b.ea for b in cmsg.blocks], [addr])
|
||
|
|
fakeret_edges = [e for e in cmsg.graph.edges if e.dst_ea == fakeret_dst_addr]
|
||
|
|
self.assertEqual(len(fakeret_edges), 1)
|
||
|
|
self.assertFalse(
|
||
|
|
bool(fakeret_edges[0].is_outside),
|
||
|
|
"test setup: edge must have is_outside=False",
|
||
|
|
)
|
||
|
|
|
||
|
|
loaded = Function.parse_from_cmessage(
|
||
|
|
cmsg,
|
||
|
|
function_manager=fm,
|
||
|
|
project=proj,
|
||
|
|
)
|
||
|
|
post = set(loaded._local_block_addrs)
|
||
|
|
self.assertEqual(
|
||
|
|
pre,
|
||
|
|
post,
|
||
|
|
f"round-trip is not idempotent: pre={sorted(hex(a) for a in pre)} post={sorted(hex(a) for a in post)}",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|