Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix constant folding & constraint set slicing #1706

Merged
merged 57 commits into from
Jun 17, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
0cd1bc5
Add ONE failing test
feliam May 13, 2020
609a37e
Fix test
feliam May 13, 2020
b29f3ac
Try fix get-related
feliam May 13, 2020
79aa5cc
Move regression to other
feliam Jun 2, 2020
7339eb9
blkn
feliam Jun 2, 2020
c121c38
blkn
feliam Jun 2, 2020
3ff6b95
Move get related
feliam Jun 5, 2020
02b4c69
Merge branch 'master' into dev-get-related
feliam Jun 5, 2020
00e8199
CC
feliam Jun 5, 2020
6b7f671
fix concolic
feliam Jun 5, 2020
6732eb2
lint
feliam Jun 5, 2020
4adeac2
DivByZero default to zero
feliam Jun 5, 2020
7565b4a
blkn
feliam Jun 5, 2020
1680397
Update manticore/core/smtlib/solver.py
feliam Jun 5, 2020
cbe55b9
Update manticore/core/smtlib/constraints.py
feliam Jun 5, 2020
315ef64
Update manticore/core/smtlib/constraints.py
feliam Jun 5, 2020
6155a4d
remove odd string
feliam Jun 5, 2020
38dbfd6
Merge branch 'dev-get-related' of github.com:trailofbits/manticore in…
feliam Jun 5, 2020
1491cbd
lint
feliam Jun 5, 2020
264eb04
mypy lint
feliam Jun 5, 2020
e0130a4
Update manticore/core/smtlib/visitors.py
feliam Jun 5, 2020
d0e9abf
lint
feliam Jun 5, 2020
9807b37
Add Docs
feliam Jun 8, 2020
728e021
Merge branch 'master' into dev-get-related
feliam Jun 9, 2020
e584fb2
merge
feliam Jun 9, 2020
bf3a2b9
Replace modulo with masks
Jun 10, 2020
3eb96f8
Blacken
Jun 10, 2020
5e54189
blkn
feliam Jun 10, 2020
a0ab429
Merge branch 'dev-get-related' of github.com:trailofbits/manticore in…
feliam Jun 10, 2020
536ec71
fix mypy
feliam Jun 10, 2020
a83e8c2
fix mypy
feliam Jun 10, 2020
37144d9
Add tests for signed LT behavior
Jun 10, 2020
1b8754d
New test
feliam Jun 10, 2020
787e51f
Fix constant folding
feliam Jun 10, 2020
dca9adb
merge
feliam Jun 10, 2020
dfa7a84
lint
feliam Jun 10, 2020
b841038
lint
feliam Jun 10, 2020
735a963
lint
feliam Jun 10, 2020
b54a448
Unittesting power
feliam Jun 10, 2020
02563cd
Permisive read_buffer
feliam Jun 10, 2020
3fab981
Preserve precision in constant folding
Jun 10, 2020
f7fb8bb
Strip left-in print debugging
Jun 11, 2020
2f5d4d2
fix wasm
feliam Jun 11, 2020
18064f8
Merge branch 'dev-get-related' of github.com:trailofbits/manticore in…
feliam Jun 11, 2020
52d8eb4
Fix
feliam Jun 11, 2020
f1dfb80
REmove get_related from the default path and fix arm test
feliam Jun 12, 2020
d5b577e
blkn
feliam Jun 12, 2020
79b42ed
fix related to tests
feliam Jun 12, 2020
ec8489f
blkn
feliam Jun 12, 2020
2113a73
Fix bug in test and disable debug messages in Solver
feliam Jun 12, 2020
c2a21ba
smtlib config to disable multiple check-sat in newer z3
feliam Jun 12, 2020
f87e1e2
Disable log test and fix merging poc vs variable migration
feliam Jun 15, 2020
2eb630d
blkn
feliam Jun 15, 2020
8818ea3
Avoid exception in some callbacks
feliam Jun 15, 2020
27db1b8
can_raise at did_will
feliam Jun 15, 2020
64a03a7
slightly better z3 onfiguration
feliam Jun 16, 2020
380fb31
lint
feliam Jun 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion examples/script/concolic.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from manticore.core.plugin import ExtendedTracer, Follower, Plugin
from manticore.core.smtlib.constraints import ConstraintSet
from manticore.core.smtlib.solver import Z3Solver
from manticore.core.smtlib.visitors import GetDeclarations

from manticore.utils import config

import copy
Expand Down Expand Up @@ -136,8 +138,17 @@ def perm(lst, func):


def constraints_to_constraintset(constupl):
# originally those constraints belonged to a different ConstraintSet
# This is a hack
x = ConstraintSet()
x._constraints = list(constupl)

declarations = GetDeclarations()
for a in constupl:
declarations.visit(a)
x.add(a)
for d in declarations.result:
x._declare(d)

return x


Expand Down
98 changes: 54 additions & 44 deletions manticore/core/smtlib/constraints.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import itertools
import sys

from typing import Optional
from ...utils.helpers import PickleSerializer
from ...exceptions import SmtlibError
from .expression import (
Expression,
BitVecVariable,
BoolVariable,
ArrayVariable,
Expand Down Expand Up @@ -115,7 +116,7 @@ def _get_sid(self) -> int:
self._sid += 1
return self._sid

def __get_related(self, related_to=None):
def related_to(self, related_to: Optional[Expression] = None):
feliam marked this conversation as resolved.
Show resolved Hide resolved
# sam.moelius: There is a flaw in how __get_related works: when called on certain
# unsatisfiable sets, it can return a satisfiable one. The flaw arises when:
# * self consists of a single constraint C
Expand All @@ -127,49 +128,57 @@ def __get_related(self, related_to=None):
# set. Thus, __get_related was called on an unsatisfiable set, {C}, but it returned a
# satisfiable one, {}.
# In light of the above, the core __get_related logic is currently disabled.
# if related_to is not None:
# feliam: This assumes the previous constraints are already SAT (normal SE forking)
if consts.related_constraints and related_to is not None:
number_of_constraints = len(self.constraints)
remaining_constraints = set(self.constraints)
related_variables = get_variables(related_to)
related_constraints = set()

added = True
while added:
added = False
logger.debug("Related variables %r", [x.name for x in related_variables])
for constraint in list(remaining_constraints):
if isinstance(constraint, BoolConstant):
if constraint.value:
continue
else:
related_constraints = {constraint}
break

variables = get_variables(constraint)
if related_variables & variables or not (variables):
remaining_constraints.remove(constraint)
related_constraints.add(constraint)
related_variables |= variables
added = True

logger.debug(
"Reduced %d constraints!!", number_of_constraints - len(related_constraints)
)
else:
related_variables = set()
for constraint in self.constraints:
related_variables |= get_variables(constraint)
related_constraints = set(self.constraints)
return related_variables, related_constraints

def to_string(self, related_to=None, replace_constants=False):
related_variables, related_constraints = self.__get_related(related_to)
"""
Slices this ConstraintSet keeping only the related constraints.
Two constraints are independient if they can be expressed full using a
disjoint set of variables.
Todo: Research. constraints refering differen not overlapping parts of the same array
should be considered independient.
:param related_to: An expression
:return:
"""
if related_to is None:
return self
number_of_constraints = len(self.constraints)
remaining_constraints = set(self.constraints)
related_variables = get_variables(related_to)
related_constraints = set()

added = True
while added:
added = False
logger.debug("Related variables %r", [x.name for x in related_variables])
for constraint in list(remaining_constraints):
if isinstance(constraint, BoolConstant):
if constraint.value:
continue
else:
related_constraints = {constraint}
break

variables = get_variables(constraint)
if related_variables & variables or not (variables):
remaining_constraints.remove(constraint)
related_constraints.add(constraint)
related_variables |= variables
added = True

logger.debug("Reduced %d constraints!!", number_of_constraints - len(related_constraints))

# related_variables, related_constraints
cs = ConstraintSet()
for var in related_variables:
cs._declare(var)
for constraint in related_constraints:
cs.add(constraint)
return cs

def to_string(self, replace_constants=True):
feliam marked this conversation as resolved.
Show resolved Hide resolved
variables, constraints = self.get_declared_variables(), self.constraints

if replace_constants:
constant_bindings = {}
for expression in related_constraints:
for expression in constraints:
if (
isinstance(expression, BoolEqual)
and isinstance(expression.operands[0], Variable)
Expand All @@ -179,16 +188,17 @@ def to_string(self, related_to=None, replace_constants=False):

tmp = set()
result = ""
for var in related_variables:
for var in variables:
# FIXME
# band aid hack around the fact that we are double declaring stuff :( :(
if var.declaration in tmp:
logger.warning("Variable '%s' was copied twice somewhere", var.name)
continue
tmp.add(var.declaration)
result += var.declaration + "\n"

translator = TranslatorSmtlib(use_bindings=True)
for constraint in related_constraints:
for constraint in constraints:
if replace_constants:
constraint = simplify(replace(constraint, constant_bindings))
# if no variables then it is a constant
Expand Down
23 changes: 16 additions & 7 deletions manticore/core/smtlib/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ def can_be_true(self, constraints: ConstraintSet, expression: Union[bool, Bool]

with constraints as temp_cs:
temp_cs.add(expression)
self._reset(temp_cs.to_string(related_to=expression))
self._reset(temp_cs.to_string())
return self._is_sat()

# get-all-values min max minmax
Expand All @@ -454,7 +454,7 @@ def get_all_values(self, constraints, expression, maxcnt=None, silent=False):
maxcnt = 2
silent = True

with constraints as temp_cs:
with constraints.related_to(expression) as temp_cs:
if isinstance(expression, Bool):
var = temp_cs.new_bool()
elif isinstance(expression, BitVec):
Expand All @@ -471,7 +471,7 @@ def get_all_values(self, constraints, expression, maxcnt=None, silent=False):
)

temp_cs.add(var == expression)
self._reset(temp_cs.to_string(related_to=var))
self._reset(temp_cs.related_to(var).to_string())
result = []
start = time.time()
while self._is_sat():
Expand All @@ -490,11 +490,20 @@ def get_all_values(self, constraints, expression, maxcnt=None, silent=False):
if time.time() - start > consts.timeout:
if silent:
logger.info("Timeout searching for all solutions")
return result
return (
result
/ home
/ felipe
/ Projects
/ manticore
/ tests
/ other
/ test_smtlibv2.py
feliam marked this conversation as resolved.
Show resolved Hide resolved
)
raise SolverError("Timeout")
# Sometimes adding a new contraint after a check-sat eats all the mem
temp_cs.add(var != value)
self._reset(temp_cs.to_string(related_to=var))
self._reset(temp_cs.to_string())
# self._assert(var != value)
return list(result)

Expand All @@ -516,8 +525,8 @@ def optimize(self, constraints: ConstraintSet, x: BitVec, goal: str, max_iter=10
X = temp_cs.new_bitvec(x.size)
temp_cs.add(X == x)
aux = temp_cs.new_bitvec(X.size, name="optimized_")
self._reset(temp_cs.to_string(related_to=X))
self._send(aux.declaration)
self._reset(temp_cs.to_string())
# self._send(aux.declaration)
feliam marked this conversation as resolved.
Show resolved Hide resolved

start = time.time()
if consts.optimize and getattr(self, f"support_{goal}", False):
Expand Down
19 changes: 17 additions & 2 deletions manticore/core/smtlib/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ def __init__(self, **kw):
BitVecAdd: operator.__add__,
BitVecSub: operator.__sub__,
BitVecMul: operator.__mul__,
BitVecDiv: operator.__floordiv__,
BitVecShiftLeft: operator.__lshift__,
BitVecShiftRight: operator.__rshift__,
BitVecAnd: operator.__and__,
Expand All @@ -301,13 +300,29 @@ def __init__(self, **kw):
BoolAnd: operator.__and__,
BoolOr: operator.__or__,
BoolNot: operator.__not__,
BitVecUnsignedDiv: lambda x, y: (x & UNSIGN_MASK) // (y & UNSIGN_MASK),
BitVecUnsignedDiv: lambda x, y: 0 if (y & UNSIGN_MASK) == 0 else (x & UNSIGN_MASK) // (y & UNSIGN_MASK),
UnsignedLessThan: lambda x, y: (x & UNSIGN_MASK) < (y & UNSIGN_MASK),
UnsignedLessOrEqual: lambda x, y: (x & UNSIGN_MASK) <= (y & UNSIGN_MASK),
UnsignedGreaterThan: lambda x, y: (x & UNSIGN_MASK) > (y & UNSIGN_MASK),
UnsignedGreaterOrEqual: lambda x, y: (x & UNSIGN_MASK) >= (y & UNSIGN_MASK),
}

def visit_BitVecDiv(self, expression, *operands):
feliam marked this conversation as resolved.
Show resolved Hide resolved
if all(isinstance(o, Constant) for o in operands):
signmask = operands[0].signmask
mask = operands[0].mask
numeral = operands[0].value
dividend = operands[1].value
if numeral & signmask:
numeral = -(mask - numeral - 1)
if dividend & signmask:
dividend = -(mask - dividend - 1)
if dividend == 0:
result = 0
else:
result = int(numeral / dividend)
return BitVecConstant(expression.size, result, taint=expression.taint)

def visit_BitVecConcat(self, expression, *operands):
if all(isinstance(o, Constant) for o in operands):
result = 0
Expand Down
Binary file added tests/other/data/ErrRelated.pkl.gz
Binary file not shown.
71 changes: 69 additions & 2 deletions tests/other/test_smtlibv2.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
import os

from manticore.core.smtlib import (
ConstraintSet,
Expand All @@ -11,15 +12,55 @@
arithmetic_simplify,
constant_folder,
replace,
BitVecConstant,
)
from manticore.core.smtlib.solver import Z3Solver
from manticore.core.smtlib.expression import *
from manticore.utils.helpers import pickle_dumps
from manticore import config

# logging.basicConfig(filename = "test.log",
# format = "%(asctime)s: %(name)s:%(levelname)s: %(message)s",
# level = logging.DEBUG)

DIRPATH = os.path.dirname(__file__)


class RegressionTest(unittest.TestCase):
def test_related_to(self):
import gzip
import pickle, sys

filename = os.path.abspath(os.path.join(DIRPATH, "data", "ErrRelated.pkl.gz"))
feliam marked this conversation as resolved.
Show resolved Hide resolved

constraints, constraint = pickle.loads(gzip.open(filename, "rb").read())

consts = config.get_group("smt")
consts.related_constraints = False

Z3Solver.instance().can_be_true.cache_clear()
ground_truth = Z3Solver.instance().can_be_true(constraints, constraint)
self.assertEqual(ground_truth, False)

consts.related_constraints = True
Z3Solver.instance().can_be_true.cache_clear()
self.assertEqual(ground_truth, Z3Solver.instance().can_be_true(constraints, constraint))

# Replace
new_constraint = Operators.UGE(
Operators.SEXTEND(BitVecConstant(256, 0x1A), 256, 512) * BitVecConstant(512, 1),
0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000,
)
self.assertEqual(translate_to_smtlib(constraint), translate_to_smtlib(new_constraint))

consts.related_constraints = False
Z3Solver.instance().can_be_true.cache_clear()
self.assertEqual(ground_truth, Z3Solver.instance().can_be_true(constraints, new_constraint))

consts.related_constraints = True
Z3Solver.instance().can_be_true.cache_clear()
self.assertEqual(ground_truth, Z3Solver.instance().can_be_true(constraints, new_constraint))


class ExpressionTest(unittest.TestCase):
_multiprocess_can_split_ = True
Expand Down Expand Up @@ -905,9 +946,8 @@ def test_SDIV(self):
cs.add(b == 0x86) # -122
cs.add(c == 0x11) # 17
cs.add(a == Operators.SDIV(b, c))
cs.add(d == b / c)
cs.add(d == (b // c))
cs.add(a == d)

self.assertTrue(solver.check(cs))
self.assertEqual(solver.get_value(cs, a), -7 & 0xFF)

Expand Down Expand Up @@ -986,6 +1026,33 @@ def test_check_solver_undefined(self):
)
self.assertTrue(self.solver._solver_version() > Version(major=4, minor=4, patch=1))

def testRelated(self):
cs = ConstraintSet()
aa1 = cs.new_bool(name="AA1")
aa2 = cs.new_bool(name="AA2")
bb1 = cs.new_bool(name="BB1")
bb2 = cs.new_bool(name="BB2")
cs.add(Operators.OR(aa1, aa2))
cs.add(Operators.OR(bb1, bb2))
self.assertTrue(self.solver.check(cs))
# No BB variables related to AA
self.assertNotIn("BB", cs.related_to(aa1).to_string())
self.assertNotIn("BB", cs.related_to(aa2).to_string())
self.assertNotIn("BB", cs.related_to(aa1 == aa2).to_string())
self.assertNotIn("BB", cs.related_to(aa1 == False).to_string())
# No AA variables related to BB
self.assertNotIn("AA", cs.related_to(bb1).to_string())
self.assertNotIn("AA", cs.related_to(bb2).to_string())
self.assertNotIn("AA", cs.related_to(bb1 == bb2).to_string())
self.assertNotIn("AA", cs.related_to(bb1 == False).to_string())

# Nothing is related to tautologies?
self.assertEqual("", cs.related_to(simplify(bb1 == bb1)).to_string())

# But if the tautollogy can not get simplified we have to ask the solver
# and send in all the other stuff
self.assertNotIn("AA", cs.related_to(bb1 == bb1).to_string())

ekilmer marked this conversation as resolved.
Show resolved Hide resolved
def test_API(self):
"""
As we've split up the Constant, Variable, and Operation classes to avoid using multiple inheritance,
Expand Down