Skip to content

Commit 70589ba

Browse files
authored
Merge pull request #641 from ethereum/upd-more-precise-smt-address-encoding
Update more precise smt address encoding PR
2 parents dd3e07d + 52e7187 commit 70589ba

File tree

6 files changed

+76
-21
lines changed

6 files changed

+76
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
and continue running, whenever possible
1717
- STATICCALL abstraction is now performed in case of symbolic arguments
1818
- Better error messages for JSON parsing
19+
- Aliasing works much better for symbolic and concrete addresses
1920
- Constant propagation for symbolic values
2021

2122
## Fixed

src/EVM.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,13 +1514,24 @@ accountEmpty c =
15141514
-- TODO: handle symbolic balance...
15151515
&& c.balance == Lit 0
15161516

1517+
-- Adds constraints such that two symbolic addresses cannot alias each other
1518+
-- and symbolic addresses cannot alias concrete addresses
1519+
addAliasConstraints :: EVM t s ()
1520+
addAliasConstraints = do
1521+
vm <- get
1522+
let addrConstr = noClash $ Map.keys vm.env.contracts
1523+
modifying #constraints ((++) addrConstr)
1524+
where
1525+
noClash addrs = [a ./= b | a <- addrs, b <- addrs, Expr.isSymAddr b, a < b]
1526+
15171527
-- * How to finalize a transaction
15181528
finalize :: VMOps t => EVM t s ()
15191529
finalize = do
15201530
let
15211531
revertContracts = use (#tx % #txReversion) >>= assign (#env % #contracts)
15221532
revertSubstate = assign (#tx % #subState) (SubState mempty mempty mempty mempty mempty mempty)
15231533

1534+
addAliasConstraints
15241535
use #result >>= \case
15251536
Just (VMFailure (Revert _)) -> do
15261537
revertContracts
@@ -2141,23 +2152,30 @@ create :: forall t s. (?op :: Word8, VMOps t)
21412152
-> Expr EWord -> Gas t -> Expr EWord -> [Expr EWord] -> Expr EAddr -> Expr Buf -> EVM t s ()
21422153
create self this xSize xGas xValue xs newAddr initCode = do
21432154
vm0 <- get
2155+
-- are we exceeding the max init code size
21442156
if xSize > Lit (vm0.block.maxCodeSize * 2)
21452157
then do
21462158
assign (#state % #stack) (Lit 0 : xs)
21472159
assign (#state % #returndata) mempty
21482160
vmError $ MaxInitCodeSizeExceeded (vm0.block.maxCodeSize * 2) xSize
2161+
-- are we overflowing the nonce
21492162
else if this.nonce == Just maxBound
21502163
then do
21512164
assign (#state % #stack) (Lit 0 : xs)
21522165
assign (#state % #returndata) mempty
21532166
pushTrace $ ErrorTrace NonceOverflow
21542167
next
2168+
-- are we overflowing the stack
21552169
else if length vm0.frames >= 1024
21562170
then do
21572171
assign (#state % #stack) (Lit 0 : xs)
21582172
assign (#state % #returndata) mempty
21592173
pushTrace $ ErrorTrace CallDepthLimitReached
21602174
next
2175+
-- are we deploying to an address that already has a contract?
2176+
-- note: the symbolic interpreter generates constraints ensuring that
2177+
-- symbolic storage keys cannot alias other storage keys, making this check
2178+
-- safe to perform statically
21612179
else if collision $ Map.lookup newAddr vm0.env.contracts
21622180
then burn' xGas $ do
21632181
assign (#state % #stack) (Lit 0 : xs)

src/EVM/Expr.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,10 @@ isPartial = \case
13931393
Partial {} -> True
13941394
_ -> False
13951395

1396+
isSymAddr :: Expr EAddr -> Bool
1397+
isSymAddr (SymAddr _) = True
1398+
isSymAddr _ = False
1399+
13961400
-- | Returns the byte at idx from the given word.
13971401
indexWord :: Expr EWord -> Expr EWord -> Expr Byte
13981402
-- Simplify masked reads:

src/EVM/SMT.hs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ assertPropsNoSimp psPreConc = do
219219
pure $ prelude
220220
<> (declareAbstractStores abstractStores)
221221
<> smt2Line ""
222-
<> (declareAddrs addresses)
222+
<> declareConstrainAddrs addresses
223223
<> smt2Line ""
224224
<> (declareBufs toDeclarePsElim bufs stores)
225225
<> smt2Line ""
@@ -268,7 +268,7 @@ assertPropsNoSimp psPreConc = do
268268
storeVals = Map.elems stores
269269
storageReads = Map.unionsWith (<>) $ fmap findStorageReads toDeclarePs
270270
abstractStores = Set.toList $ Set.unions (fmap referencedAbstractStores toDeclarePs)
271-
addresses = Set.toList $ Set.unions (fmap referencedWAddrs toDeclarePs)
271+
addresses = Set.toList $ Set.unions (fmap referencedAddrs toDeclarePs)
272272

273273
-- Keccak assertions: concrete values, distance between pairs, injectivity, etc.
274274
-- This will make sure concrete values of Keccak are asserted, if they can be computed (i.e. can be concretized)
@@ -294,11 +294,11 @@ referencedAbstractStores term = foldTerm go mempty term
294294
AbstractStore s idx -> Set.singleton (storeName s idx)
295295
_ -> mempty
296296

297-
referencedWAddrs :: TraversableTerm a => a -> Set Builder
298-
referencedWAddrs term = foldTerm go mempty term
297+
referencedAddrs :: TraversableTerm a => a -> Set Builder
298+
referencedAddrs term = foldTerm go mempty term
299299
where
300300
go = \case
301-
WAddr(a@(SymAddr _)) -> Set.singleton (formatEAddr a)
301+
SymAddr a -> Set.singleton (formatEAddr (SymAddr a))
302302
_ -> mempty
303303

304304
referencedBufs :: TraversableTerm a => a -> [Builder]
@@ -435,10 +435,12 @@ declareVars names = SMT2 (["; variables"] <> fmap declare names) cexvars mempty
435435
cexvars = (mempty :: CexVars){ calldata = fmap toLazyText names }
436436

437437
-- Given a list of variable names, create an SMT2 object with the variables declared
438-
declareAddrs :: [Builder] -> SMT2
439-
declareAddrs names = SMT2 (["; symbolic addresseses"] <> fmap declare names) cexvars mempty
438+
declareConstrainAddrs :: [Builder] -> SMT2
439+
declareConstrainAddrs names = SMT2 (["; concrete and symbolic addresses"] <> fmap declare names <> fmap assume names) cexvars mempty
440440
where
441441
declare n = "(declare-fun " <> n <> " () Addr)"
442+
-- assume that symbolic addresses do not collide with the zero address or precompiles
443+
assume n = "(assert (bvugt " <> n <> " (_ bv9 160)))"
442444
cexvars = (mempty :: CexVars){ addrs = fmap toLazyText names }
443445

444446
enforceGasOrder :: [Prop] -> SMT2
@@ -869,6 +871,7 @@ exprToSMT = \case
869871
encPrev <- exprToSMT prev
870872
pure $ "(store" `sp` encPrev `sp` encIdx `sp` encVal <> ")"
871873
SLoad idx store -> op2 "select" store idx
874+
LitAddr n -> pure $ fromLazyText $ "(_ bv" <> T.pack (show (into n :: Integer)) <> " 160)"
872875
Gas freshVar -> pure $ fromLazyText $ "gas_" <> (T.pack $ show freshVar)
873876

874877
a -> internalError $ "TODO: implement: " <> show a

src/EVM/SymExec.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,8 @@ runExpr = do
501501
let traces = TraceContext (Zipper.toForest vm.traces) vm.env.contracts vm.labels
502502
pure $ case vm.result of
503503
Just (VMSuccess buf) -> Success vm.constraints traces buf (fmap toEContract vm.env.contracts)
504-
Just (VMFailure e) -> Failure vm.constraints traces e
505-
Just (Unfinished p) -> Partial vm.constraints traces p
504+
Just (VMFailure e) -> Failure vm.constraints traces e
505+
Just (Unfinished p) -> Partial vm.constraints traces p
506506
_ -> internalError "vm in intermediate state after call to runFully"
507507

508508
toEContract :: Contract -> Expr EContract
@@ -963,7 +963,7 @@ prettyCalldata cex buf sig types = headErr errSig (T.splitOn "(" sig) <> "(" <>
963963
ConcreteBuf c -> T.pack (bsToHex c)
964964
_ -> err
965965
SAbi _ -> err
966-
headErr e l = fromMaybe e $ listToMaybe l
966+
headErr e l = fromMaybe e $ listToMaybe l
967967
err = internalError $ "unable to produce a concrete model for calldata: " <> show buf
968968
errSig = internalError $ "unable to split sig: " <> show sig
969969

test/test.hs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,15 +1809,29 @@ tests = testGroup "hevm"
18091809
Just c <- solcRuntime "C" src
18101810
res <- reachableUserAsserts c Nothing
18111811
assertBoolM "unexpected cex" (isRight res)
1812-
-- TODO: implement missing aliasing rules
1813-
, expectFail $ test "deployed-contract-addresses-cannot-alias" $ do
1812+
, test "deployed-contract-addresses-cannot-alias1" $ do
18141813
Just c <- solcRuntime "C"
18151814
[i|
18161815
contract A {}
18171816
contract C {
18181817
function f() external {
18191818
A a = new A();
1820-
if (address(a) == address(this)) assert(false);
1819+
uint256 addr = uint256(uint160(address(a)));
1820+
uint256 addr2 = uint256(uint160(address(this)));
1821+
assert(addr != addr2);
1822+
}
1823+
}
1824+
|]
1825+
res <- reachableUserAsserts c Nothing
1826+
assertBoolM "should not be able to alias" (isRight res)
1827+
, test "deployed-contract-addresses-cannot-alias2" $ do
1828+
Just c <- solcRuntime "C"
1829+
[i|
1830+
contract A {}
1831+
contract C {
1832+
function f() external {
1833+
A a = new A();
1834+
assert(address(a) != address(this));
18211835
}
18221836
}
18231837
|]
@@ -2012,36 +2026,50 @@ tests = testGroup "hevm"
20122026
Right e <- reachableUserAsserts c Nothing
20132027
-- TODO: this should work one day
20142028
assertBoolM "should be partial" (Expr.containsNode isPartial e)
2029+
, test "symbolic-addresses-cannot-be-zero-or-precompiles" $ do
2030+
let addrs = [T.pack . show . Addr $ a | a <- [0x0..0x09]]
2031+
mkC a = fromJust <$> solcRuntime "A"
2032+
[i|
2033+
contract A {
2034+
function f() external {
2035+
assert(msg.sender != address(${a}));
2036+
}
2037+
}
2038+
|]
2039+
codes <- mapM mkC addrs
2040+
results <- mapM (flip reachableUserAsserts (Just (Sig "f()" []))) codes
2041+
let ok = and $ fmap (isRight) results
2042+
assertBoolM "unexpected cex" ok
20152043
, test "addresses-in-context-are-symbolic" $ do
20162044
Just a <- solcRuntime "A"
20172045
[i|
20182046
contract A {
20192047
function f() external {
2020-
assert(msg.sender != address(0x0));
2048+
assert(msg.sender != address(0x10));
20212049
}
20222050
}
20232051
|]
20242052
Just b <- solcRuntime "B"
20252053
[i|
20262054
contract B {
20272055
function f() external {
2028-
assert(block.coinbase != address(0x1));
2056+
assert(block.coinbase != address(0x11));
20292057
}
20302058
}
20312059
|]
20322060
Just c <- solcRuntime "C"
20332061
[i|
20342062
contract C {
20352063
function f() external {
2036-
assert(tx.origin != address(0x2));
2064+
assert(tx.origin != address(0x12));
20372065
}
20382066
}
20392067
|]
20402068
Just d <- solcRuntime "D"
20412069
[i|
20422070
contract D {
20432071
function f() external {
2044-
assert(address(this) != address(0x3));
2072+
assert(address(this) != address(0x13));
20452073
}
20462074
}
20472075
|]
@@ -2050,10 +2078,11 @@ tests = testGroup "hevm"
20502078
assertEqualM "wrong number of addresses" 1 (length (Map.keys cex.addrs))
20512079
pure cex
20522080

2053-
assertEqualM "wrong model for a" (Addr 0) (fromJust $ Map.lookup (SymAddr "caller") acex.addrs)
2054-
assertEqualM "wrong model for b" (Addr 1) (fromJust $ Map.lookup (SymAddr "coinbase") bcex.addrs)
2055-
assertEqualM "wrong model for c" (Addr 2) (fromJust $ Map.lookup (SymAddr "origin") ccex.addrs)
2056-
assertEqualM "wrong model for d" (Addr 3) (fromJust $ Map.lookup (SymAddr "entrypoint") dcex.addrs)
2081+
-- Lowest allowed address is 0x10 due to reserved addresses up to 0x9
2082+
assertEqualM "wrong model for a" (Addr 0x10) (fromJust $ Map.lookup (SymAddr "caller") acex.addrs)
2083+
assertEqualM "wrong model for b" (Addr 0x11) (fromJust $ Map.lookup (SymAddr "coinbase") bcex.addrs)
2084+
assertEqualM "wrong model for c" (Addr 0x12) (fromJust $ Map.lookup (SymAddr "origin") ccex.addrs)
2085+
assertEqualM "wrong model for d" (Addr 0x13) (fromJust $ Map.lookup (SymAddr "entrypoint") dcex.addrs)
20572086
]
20582087
, testGroup "Symbolic execution"
20592088
[

0 commit comments

Comments
 (0)