mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
variable_recovery: don't add call-arg type constraints from guessed prototypes' integer params
Integer parameter types in guessed prototypes are size-based defaults, not inferred types. Using them as call-site constraints can conflict with pointer constraints on the same variable from other call sites, collapsing the type solution to bottom and degrading recovered pointer parameters to plain integers. Skip them; pointer/struct/float parameters and non-guessed prototypes are unaffected.
This commit is contained in:
parent
36587c513a
commit
17373ff40c
2 changed files with 82 additions and 3 deletions
|
|
@ -249,7 +249,14 @@ class SimEngineVRAIL(
|
|||
isinstance(expr.target, (ailment.Expr.Const, str))
|
||||
or expr.tags.get("is_prototype_guessed", True) is False
|
||||
) and expr.args is not None:
|
||||
self._call_add_arg_based_type_constraints(prototype, prototype_libname, args, list(expr.args))
|
||||
prototype_guessed = (
|
||||
func.is_prototype_guessed
|
||||
if func is not None
|
||||
else expr.tags.get("is_prototype_guessed", True) is not False
|
||||
)
|
||||
self._call_add_arg_based_type_constraints(
|
||||
prototype, prototype_libname, args, list(expr.args), prototype_guessed=prototype_guessed
|
||||
)
|
||||
# handle return type
|
||||
if not expr.tags.get("is_prototype_guessed", True):
|
||||
return_ty = self.type_lifter.lift(prototype.returnty) # type: ignore
|
||||
|
|
@ -322,7 +329,14 @@ class SimEngineVRAIL(
|
|||
isinstance(stmt.expr.target, (ailment.Expr.Const, str))
|
||||
or stmt.tags.get("is_prototype_guessed", True) is False
|
||||
):
|
||||
self._call_add_arg_based_type_constraints(prototype, prototype_libname, args, stmt.expr.args)
|
||||
prototype_guessed = (
|
||||
func.is_prototype_guessed
|
||||
if func is not None
|
||||
else stmt.tags.get("is_prototype_guessed", True) is not False
|
||||
)
|
||||
self._call_add_arg_based_type_constraints(
|
||||
prototype, prototype_libname, args, stmt.expr.args, prototype_guessed=prototype_guessed
|
||||
)
|
||||
# handle return type
|
||||
return_ty = self.type_lifter.lift(prototype.returnty) # type: ignore
|
||||
ret_ty = self.tv_manager.new_tv()
|
||||
|
|
@ -362,7 +376,12 @@ class SimEngineVRAIL(
|
|||
)
|
||||
|
||||
def _call_add_arg_based_type_constraints(
|
||||
self, prototype: SimTypeFunction, prototype_libname: str | None, args: list, arg_atoms: list
|
||||
self,
|
||||
prototype: SimTypeFunction,
|
||||
prototype_libname: str | None,
|
||||
args: list,
|
||||
arg_atoms: list,
|
||||
prototype_guessed: bool = False,
|
||||
) -> None:
|
||||
# add type constraints
|
||||
if not args:
|
||||
|
|
@ -373,6 +392,12 @@ class SimEngineVRAIL(
|
|||
continue
|
||||
arg_type = dereference_simtype_by_lib(arg_type, prototype_libname) if prototype_libname else arg_type
|
||||
arg_ty = self.type_lifter.lift(arg_type)
|
||||
if prototype_guessed and isinstance(arg_ty, typeconsts.Int) and not isinstance(arg_ty, typeconsts.Pointer):
|
||||
# plain integer parameter types in guessed prototypes are usually size-based defaults and carry
|
||||
# no real type information. adding them as constraints can conflict with precise (e.g., pointer)
|
||||
# constraints from other call sites and degrade the solution to the bottom type. skip them;
|
||||
# informative types (pointers, structs, floats) from guessed prototypes are still used.
|
||||
continue
|
||||
if arg.typevar is not None and isinstance(
|
||||
arg_ty, (typeconsts.TypeConstant, typevars.TypeVariable, typevars.DerivedTypeVariable)
|
||||
):
|
||||
|
|
|
|||
|
|
@ -7,8 +7,16 @@ import logging
|
|||
import os
|
||||
import unittest
|
||||
|
||||
import archinfo
|
||||
|
||||
import angr
|
||||
from angr.analyses.typehoon import typeconsts
|
||||
from angr.analyses.typehoon.translator import TypeTranslator
|
||||
from angr.analyses.typehoon.typevars import Subtype, TypeVariable
|
||||
from angr.analyses.variable_recovery.engine_ail import SimEngineVRAIL
|
||||
from angr.analyses.variable_recovery.engine_base import RichR
|
||||
from angr.knowledge_plugins.variables import VariableType
|
||||
from angr.sim_type import SimTypeChar, SimTypeFunction, SimTypeInt, SimTypeLongLong, SimTypePointer
|
||||
from angr.sim_variable import SimRegisterVariable, SimStackVariable
|
||||
from tests.common import bin_location, print_decompilation_result
|
||||
|
||||
|
|
@ -448,6 +456,52 @@ class TestVariableRecovery(unittest.TestCase):
|
|||
False,
|
||||
)
|
||||
|
||||
def test_guessed_prototype_int_args_do_not_constrain_caller_types(self):
|
||||
"""
|
||||
Plain integer parameter types in guessed callee prototypes are size-based defaults and must not
|
||||
generate type constraints for the caller's argument expressions: a spurious int64 upper bound
|
||||
conflicts with precise pointer constraints from other call sites (SInt64 and Pointer64 are
|
||||
siblings in the base type lattice), collapsing the solution to the bottom type and degrading
|
||||
recovered pointer arguments (e.g., a char* format string) to plain integers.
|
||||
|
||||
Informative parameter types (pointers, structs) from guessed prototypes must still be used, and
|
||||
non-guessed prototypes (e.g., libc SimProcedures) must constrain all arguments as before.
|
||||
"""
|
||||
arch = archinfo.arch_from_id("AMD64")
|
||||
engine = object.__new__(SimEngineVRAIL)
|
||||
engine.type_lifter = TypeTranslator(arch)
|
||||
constraints = []
|
||||
|
||||
class FakeState:
|
||||
@staticmethod
|
||||
def get_stack_offset(_data):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def add_type_constraint(con):
|
||||
constraints.append(con)
|
||||
|
||||
engine.state = FakeState()
|
||||
|
||||
tv_int = TypeVariable(name="arg_int")
|
||||
tv_ptr = TypeVariable(name="arg_ptr")
|
||||
args = [RichR(None, typevar=tv_int), RichR(None, typevar=tv_ptr)]
|
||||
prototype = SimTypeFunction([SimTypeLongLong(), SimTypePointer(SimTypeChar())], SimTypeInt()).with_arch(arch)
|
||||
|
||||
# guessed prototype: the plain-int parameter adds no constraint; the pointer parameter still does
|
||||
engine._call_add_arg_based_type_constraints(prototype, None, args, [None, None], prototype_guessed=True)
|
||||
assert len(constraints) == 1
|
||||
(con,) = constraints
|
||||
assert isinstance(con, Subtype)
|
||||
assert con.sub_type is tv_ptr
|
||||
assert isinstance(con.super_type, typeconsts.Pointer)
|
||||
|
||||
# non-guessed prototype: both parameters constrain the arguments, as before
|
||||
constraints.clear()
|
||||
engine._call_add_arg_based_type_constraints(prototype, None, args, [None, None], prototype_guessed=False)
|
||||
assert len(constraints) == 2
|
||||
assert {con.sub_type for con in constraints if isinstance(con, Subtype)} == {tv_int, tv_ptr}
|
||||
|
||||
def test_format_string_type_hints_sscanf(self):
|
||||
"""
|
||||
Test that VariableRecoveryFast extracts type hints from format strings.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue