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

Use the SMT solver to convert symbolic to concrete value(s) #629

Merged
merged 9 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- JoinBytes simplification rule
- New simplification rule to help deal with abi.encodeWithSelector
- More simplification rules for Props
- Using the SMT solver to get a single concrete value for a symbolic expression
and continue running, whenever possible

## Fixed
- We now try to simplify expressions fully before trying to cast them to a concrete value
Expand Down
72 changes: 43 additions & 29 deletions src/EVM.hs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,6 @@ exec1 = do
else do
let ?op = getOpW8 vm.state
let opName = getOpName vm.state

case getOp (?op) of

OpPush0 -> do
Expand Down Expand Up @@ -1380,10 +1379,10 @@ getCodeLocation :: VM t s -> CodeLocation
getCodeLocation vm = (vm.state.contract, vm.state.pc)

query :: Query t s -> EVM t s ()
query = assign #result . Just . HandleEffect . Query
query q = assign #result $ Just $ HandleEffect (Query q)

choose :: Choose s -> EVM Symbolic s ()
choose = assign #result . Just . HandleEffect . Choose
choose c = assign #result $ Just $ HandleEffect (Choose c)

-- | Construct RPC Query and halt execution until resolved
fetchAccount :: VMOps t => Expr EAddr -> (Contract -> EVM t s ()) -> EVM t s ()
Expand All @@ -1403,11 +1402,11 @@ fetchAccount addr continue =
Nothing -> do
base <- use (#config % #baseState)
assign (#result) . Just . HandleEffect . Query $
PleaseFetchContract a base
(\c -> do assign (#cache % #fetched % at a) (Just c)
assign (#env % #contracts % at addr) (Just c)
assign #result Nothing
continue c)
PleaseFetchContract a base $ \c -> do
assign (#cache % #fetched % at a) (Just c)
assign (#env % #contracts % at addr) (Just c)
assign #result Nothing
continue c
GVar _ -> internalError "Unexpected GVar"

accessStorage
Expand Down Expand Up @@ -1446,13 +1445,11 @@ accessStorage addr slot continue = do
else do
modifying (#env % #contracts % ix addr % #storage) (writeStorage slot (Lit 0))
continue $ Lit 0
mkQuery a s = query $
PleaseFetchSlot a s
(\x -> do
mkQuery a s = query $ PleaseFetchSlot a s $ \x -> do
modifying (#cache % #fetched % ix a % #storage) (writeStorage (Lit s) (Lit x))
modifying (#env % #contracts % ix (LitAddr a) % #storage) (writeStorage (Lit s) (Lit x))
assign #result Nothing
continue (Lit x))
continue $ Lit x

accessTStorage
:: VMOps t => Expr EAddr
Expand Down Expand Up @@ -1585,24 +1582,29 @@ notStatic continue = do

forceAddr :: VMOps t => Expr EWord -> String -> (Expr EAddr -> EVM t s ()) -> EVM t s ()
forceAddr n msg continue = case wordToAddr n of
Nothing -> do
vm <- get
partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) msg (wrap [n])
Nothing -> oneSolution n $ \case
Just sol -> continue $ LitAddr (truncateToAddr sol)
Nothing -> fallback
Just c -> continue c
where fallback = do
vm <- get
partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) msg (wrap [n])

forceConcrete :: VMOps t => Expr EWord -> String -> (W256 -> EVM t s ()) -> EVM t s ()
forceConcrete n msg continue = case maybeLitWordSimp n of
Nothing -> do
vm <- get
partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) msg (wrap [n])
Nothing -> oneSolution n $ maybe fallback continue
Just c -> continue c
where fallback = do
vm <- get
partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) msg (wrap [n])

forceConcreteAddr :: VMOps t => Expr EAddr -> String -> (Addr -> EVM t s ()) -> EVM t s ()
forceConcreteAddr n msg continue = case maybeLitAddrSimp n of
Nothing -> do
vm <- get
partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) msg (wrap [n])
Nothing -> oneSolution (WAddr n) $ maybe fallback $ \c -> continue $ unsafeInto c
Just c -> continue c
where fallback = do
vm <- get
partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) msg (wrap [n])

forceConcreteAddr2 :: VMOps t => (Expr EAddr, Expr EAddr) -> String -> ((Addr, Addr) -> EVM t s ()) -> EVM t s ()
forceConcreteAddr2 (n,m) msg continue = case (maybeLitAddrSimp n, maybeLitAddrSimp m) of
Expand Down Expand Up @@ -1699,8 +1701,13 @@ cheat gas (inOffset, inSize) (outOffset, outSize) xs = do
, context = newContext
}
case maybeLitWordSimp abi of
Nothing -> partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) "symbolic cheatcode selector" (wrap [abi])
Just (unsafeInto -> abi') ->
Nothing -> oneSolution abi $ \case
Just concAbi -> runCheat concAbi input
Nothing -> partial $ UnexpectedSymbolicArg vm.state.pc (getOpName vm.state) "symbolic cheatcode selector" (wrap [abi])
Just concAbi -> runCheat concAbi input
where
runCheat abi input = do
let abi' = unsafeInto abi
case Map.lookup abi' cheatActions of
Nothing -> vmError (BadCheatCode "Cannot understand cheatcode." abi')
Just action -> action input
Expand Down Expand Up @@ -2933,7 +2940,7 @@ instance VMOps Symbolic where
toGas _ = ()
whenSymbolicElse a _ = a

partial e = assign #result (Just (Unfinished e))
partial e = assign #result $ Just (Unfinished e)
branch cond continue = do
loc <- codeloc
pathconds <- use #constraints
Expand All @@ -2954,6 +2961,17 @@ instance VMOps Symbolic where
choosePath loc Unknown =
choose . PleaseChoosePath condSimp $ choosePath loc . Case

oneSolution ewordExpr continue = do
pathconds <- use #constraints
query $ PleaseGetSol ewordExpr pathconds $ \case
Just concVal -> do
assign #result Nothing
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remind me what is #result and why do you need to assign it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, that's the ONE thing I don't understand. But that result definitely needs to be set. I'd need to dig deeper what that thing really is. If I don't set it, it never advances the PC (program counter) and we go into an infinite loop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it should be cleared to Nothing even when it fails, actually, so not only here Just concVal -> ... but also at Nothing -> ... It's a bit mysterious. I think it contains the thing it's currently doing, and normally it's Nothing, but when something needs to happen, then it gets set to something like HandleEffect, or when it fails, VMFailure

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me see if we need to assign Nothing there, too. I have a feeling that we do, actually.

pushTo #constraints $ Expr.simplifyProp (ewordExpr .== (Lit concVal))
continue $ Just concVal
Nothing -> do
assign #result Nothing
continue Nothing

instance VMOps Concrete where
burn' n continue = do
available <- use (#state % #gas)
Expand Down Expand Up @@ -3076,16 +3094,12 @@ instance VMOps Concrete where
push (into vm.state.gas)

enoughGas cost gasCap = cost <= gasCap

subGas gasCap cost = gasCap - cost

toGas = id

whenSymbolicElse _ a = a

partial _ = internalError "won't happen during concrete exec"

branch (forceLit -> cond) continue = continue (cond > 0)
oneSolution _ _ = internalError "SMT solver should never be needed in concrete mode"

-- Create symbolic VM from concrete VM
symbolify :: VM Concrete s -> VM Symbolic s
Expand Down
37 changes: 37 additions & 0 deletions src/EVM/Fetch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Numeric.Natural (Natural)
import System.Environment (lookupEnv, getEnvironment)
import System.Process
import Control.Monad.IO.Class
import Control.Monad (when)
import EVM.Effects

-- | Abstract representation of an RPC fetch request
Expand Down Expand Up @@ -212,6 +213,10 @@ oracle solvers info q = do
-- Is is possible to satisfy the condition?
continue <$> checkBranch solvers (branchcondition ./= (Lit 0)) pathconds

PleaseGetSol symAddr pathconditions continue -> do
let pathconds = foldl' PAnd (PBool True) pathconditions
continue <$> getSolution solvers symAddr pathconds

PleaseFetchContract addr base continue -> do
contract <- case info of
Nothing -> let
Expand Down Expand Up @@ -239,6 +244,38 @@ oracle solvers info q = do

type Fetcher t m s = App m => Query t s -> m (EVM t s ())

getSolution :: forall m . App m => SolverGroup -> Expr EWord -> Prop -> m (Maybe W256)
getSolution solvers symAddr pathconditions = do
conf <- readConfig
liftIO $ do
ret <- collectSolutions [] 1 pathconditions conf
case ret of
Nothing -> pure Nothing
Just r -> case length r of
0 -> pure Nothing
-- Temporary, a future improvement can deal with more than one solution
1 -> pure $ Just (r !! 0)
_ -> pure Nothing
where
collectSolutions :: [W256] -> Int -> Prop -> Config -> IO (Maybe [W256])
collectSolutions addrs maxSols conds conf = do
if (length addrs > maxSols) then pure Nothing
else
checkSat solvers (assertProps conf [(PEq (Var "addrQuery") symAddr) .&& conds]) >>= \case
Sat (SMTCex vars _ _ _ _ _) -> case (Map.lookup (Var "addrQuery") vars) of
Just addr -> do
let newConds = PAnd conds (symAddr ./= (Lit addr))
when conf.debug $ putStrLn $ "Got one solution to symbolic query:" <> show addr <> " now have " <> show (length addrs + 1) <> " solution(s), max is: " <> show maxSols
collectSolutions (addr:addrs) maxSols newConds conf
_ -> internalError "No solution to symbolic query"
Unsat -> do
when conf.debug $ putStrLn "No more solution(s) to symbolic query."
pure $ Just addrs
-- Error or timeout, we need to be conservative
res -> do
when conf.debug $ putStrLn $ "Symbolic query result is neither SAT nor UNSAT:" <> show res
pure Nothing

-- | Checks which branches are satisfiable, checking the pathconditions for consistency
-- if the third argument is true.
-- When in debug mode, we do not want to be able to navigate to dead paths,
Expand Down
6 changes: 6 additions & 0 deletions src/EVM/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ data Query t s where
PleaseFetchContract :: Addr -> BaseState -> (Contract -> EVM t s ()) -> Query t s
PleaseFetchSlot :: Addr -> W256 -> (W256 -> EVM t s ()) -> Query t s
PleaseAskSMT :: Expr EWord -> [Prop] -> (BranchCondition -> EVM Symbolic s ()) -> Query Symbolic s
PleaseGetSol :: Expr EWord -> [Prop] -> (Maybe W256 -> EVM Symbolic s ()) -> Query Symbolic s
PleaseDoFFI :: [String] -> Map String String -> (ByteString -> EVM t s ()) -> Query t s
PleaseReadEnv :: String -> (String -> EVM t s ()) -> Query t s

Expand All @@ -617,6 +618,10 @@ instance Show (Query t s) where
(("<EVM.Query: ask SMT about "
++ show condition ++ " in context "
++ show constraints ++ ">") ++)
PleaseGetSol expr constraints _ ->
(("<EVM.Query: ask SMT to get W256 for expression "
++ show expr ++ " in context "
++ show constraints ++ ">") ++)
PleaseDoFFI cmd env _ ->
(("<EVM.Query: do ffi: " ++ (show cmd) ++ " env: " ++ (show env)) ++)
PleaseReadEnv variable _ ->
Expand Down Expand Up @@ -854,6 +859,7 @@ class VMOps (t :: VMType) where

partial :: PartialExec -> EVM t s ()
branch :: Expr EWord -> (Bool -> EVM t s ()) -> EVM t s ()
oneSolution :: Expr EWord -> (Maybe W256 -> EVM t s ()) -> EVM t s ()

-- Bytecode Representations ------------------------------------------------------------------------

Expand Down
Loading