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

Issue 448 #450

Draft
wants to merge 37 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
756cb6b
add ExprFoldable class that doesn't rely on IO
danmatichuk Sep 17, 2024
21cd4c5
add FnBindings module representing post-hoc defined functions
danmatichuk Sep 20, 2024
a71caea
overhaul the control flow sync/merge logic to use uninterpreted funct…
danmatichuk Sep 25, 2024
ac66afb
Use BoundVars in FnBindings rather than SymFn to work around groundin…
danmatichuk Sep 25, 2024
4e87864
FnBindings: use bound variables instead of functions
danmatichuk Oct 2, 2024
41ac2d3
WIP: handleProcessMerge is over-widening at the moment, see TODO
danmatichuk Oct 2, 2024
b0cadef
add "time" tag to display processing time for TraceTree elements
danmatichuk Oct 14, 2024
42ca7c7
re-enable parsedFunction cache, conservatively flushing for each node
danmatichuk Oct 18, 2024
39c3f02
add first-pass filter for concretizing memory writes
danmatichuk Oct 18, 2024
fde2e5c
ParsedFunctions: track function cache validity instead of flushing
danmatichuk Oct 21, 2024
15dbfd2
cleanup merge artifact
danmatichuk Oct 21, 2024
de0a065
Repl: output duration when using "status" command
danmatichuk Oct 21, 2024
0199fec
dockerfile: fix LegacyKeyValueFormat lint
thebendavis Oct 23, 2024
bb7dcfb
dockerfile: fix FromAsCasing lint
thebendavis Oct 23, 2024
13b8dd2
dockerfile: fix FromPlatformFlagConstDisallowed lint
thebendavis Oct 23, 2024
3c5ecc8
dockerfile: whitespace cleanup
thebendavis Oct 23, 2024
6505a3d
user manual: update docker invocations
thebendavis Oct 23, 2024
cd097a5
dockerfile: report unsupported TARGETPLATFORM
thebendavis Oct 23, 2024
738cd4f
CI: explicitly set platform for docker build
thebendavis Oct 23, 2024
7655fc2
Merge pull request #454 from GaloisInc/bd/dockerfile-cleanups
thebendavis Oct 24, 2024
e010f58
use batch processing when initializing abstract value domain
danmatichuk Oct 24, 2024
fcb2f7c
Experimenting with some limited parallelism in simple cases
danmatichuk Oct 24, 2024
49c7eeb
fixup packet integration test
danmatichuk Oct 25, 2024
7500e0c
fix bug where nodes were not outputted during 'wait' unless
danmatichuk Oct 25, 2024
6ecb882
initAbsDomainVals: handle case where a cell has multiple writes, with…
danmatichuk Oct 25, 2024
65a18a9
disable simplifcation of 'SimBundle'
danmatichuk Oct 25, 2024
d19920a
allow forked solver instances to share the sat/unsat cache
danmatichuk Oct 25, 2024
b7c0b16
move 'asScopedConst' to last rescoping strategy
danmatichuk Oct 25, 2024
97d1c09
use Crucible's assumption/frames to initialize forked solver state
danmatichuk Oct 28, 2024
c923ac3
concretize: add explicit positivity assumptions for integers
danmatichuk Oct 28, 2024
76318a0
Merge pull request #453 from GaloisInc/dm/optimize
danmatichuk Oct 28, 2024
7f08d40
Merge branch 'master' into issue-448
danmatichuk Oct 29, 2024
897acf6
FnBindings: don't generate uninterpreted functions for concrete values
danmatichuk Oct 31, 2024
3c31ec1
drop implicit SimSpec wrapping from 'withFreshScope'
danmatichuk Oct 31, 2024
9551b38
remove deprecaded 'scopeAsm' field from 'SimScope'
danmatichuk Oct 31, 2024
a1a5437
add "composite" scopes to handle mixing variables from
danmatichuk Nov 6, 2024
19cc595
WIP: rework control flow merge logic to use mixed scopes
danmatichuk Nov 6, 2024
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
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ jobs:
docker pull artifactory.galois.com:5025/pate/pate:refs-heads-master || \
echo "No latest image found"

# Docker on our self-hosted runners doesn't populate TARGETPLATFORM so we
# set it manually via build-arg. We need this to pass our platform check
# in the Dockerfile.
- name: Build Docker Image
run: |
docker build . \
docker build --platform linux/amd64 --build-arg TARGETPLATFORM=linux/amd64 . \
--cache-from artifactory.galois.com:5025/pate/pate:${GITHUB_REF//\//\-} \
--cache-from artifactory.galois.com:5025/pate/pate:refs-heads-master \
-t pate
Expand Down
19 changes: 11 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Clone git into image
FROM --platform=linux/amd64 ubuntu:20.04 as gitbase
FROM ubuntu:20.04 AS gitbase

ARG TARGETPLATFORM
RUN if [ "${TARGETPLATFORM}" != "linux/amd64" ]; then echo "TARGETPLATFORM '${TARGETPLATFORM}' is not supported, use '--platform linux/amd64'"; exit 1; fi

RUN apt update && apt install -y ssh
RUN apt install -y git
Expand Down Expand Up @@ -30,7 +33,7 @@ RUN find . -name .gitignore -exec rm {} \;


## Build project in image
FROM --platform=linux/amd64 ubuntu:20.04
FROM ubuntu:20.04
ENV GHC_VERSION=9.6.2
ENV TZ=America/Los_Angeles
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
Expand All @@ -43,13 +46,13 @@ RUN apt-get install -y yices2

RUN curl https://downloads.haskell.org/~ghcup/x86_64-linux-ghcup -o /usr/bin/ghcup && chmod +x /usr/bin/ghcup
RUN mkdir -p /root/.ghcup && ghcup install-cabal
RUN ghcup install ghc ${GHC_VERSION} && ghcup set ghc ${GHC_VERSION}
RUN ghcup install ghc ${GHC_VERSION} && ghcup set ghc ${GHC_VERSION}

RUN apt install locales
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8


######################################################################
Expand Down Expand Up @@ -82,7 +85,7 @@ RUN cabal v2-build what4
# crucible
COPY --from=gitbase /home/src/submodules/crucible /home/src/submodules/crucible
RUN cabal v2-build crucible

# arm-asl-parser
COPY --from=gitbase /home/src/submodules/arm-asl-parser /home/src/submodules/arm-asl-parser
RUN cabal v2-build asl-parser
Expand Down Expand Up @@ -114,7 +117,7 @@ RUN cabal v2-build binary-symbols flexdis86
#mutual dependency between these submodules
# macaw
COPY --from=gitbase /home/src/submodules/macaw /home/src/submodules/macaw

# macaw-loader
COPY --from=gitbase /home/src/submodules/macaw-loader /home/src/submodules/macaw-loader
RUN cabal v2-build macaw-base macaw-loader macaw-semmc
Expand Down
9 changes: 5 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The fastest way to get started is to build the Docker image and use the tool via

First, build the Docker image with the command::

docker build . -t pate
docker build --platform linux/amd64 . -t pate

Next, run the verifier on an example::

Expand Down Expand Up @@ -87,7 +87,7 @@ The verifier accepts the following command line arguments::
--read-only-segments ARG Mark segments as read-only (0-indexed) when loading
ELF
--script FILENAME Save macaw CFGs to the provided directory
--assume-stack-scope Add additional assumptions about stack frame scoping
--assume-stack-scope Add additional assumptions about stack frame scoping
during function calls (unsafe)
--ignore-warnings ARG Don't raise any of the given warning types
--always-classify-return Always resolve classifier failures by assuming
Expand All @@ -107,13 +107,14 @@ If you have a ``tar`` file of a Docker image of the verifier, you can install it

To run the verifier via Docker after this::

docker run --rm -it pate --help
docker run --rm -it --platform linux/amd64 pate --help

To make use of the verifier with Docker, it is useful to map a directory on your local filesystem into the Docker container to be able to save output files. Assuming that your original and patched binaries are ``original.exe`` and ``patched.exe``, respectively::

mkdir VerifierData
cp original.exe patched.exe VerifierData/
docker run --rm -it -v `pwd`/VerifierData`:/VerifierData pate \
docker run --rm -it --platform linux/amd64 \
-v `pwd`/VerifierData`:/VerifierData pate \
--original /VerifierData/original.exe \
--patched /VerifierData/patched.exe \
--proof-summary-json /VerifierData/report.json \
Expand Down
2 changes: 1 addition & 1 deletion docs/user-manual/build.tex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ \subsection{Recommended: Docker Container Image}
git clone [email protected]:GaloisInc/pate.git
cd pate
git submodule update --init
docker build . -t pate
docker build --platform linux/amd64 . -t pate
\end{verbatim}

\pate{} may subsequently be used via Docker, such as:
Expand Down
Binary file modified docs/user-manual/user-manual.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions pate.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ library
Pate.Verification.DemandDiscovery,
Pate.Verification.Domain,
Pate.Verification.ExternalCall,
Pate.Verification.FnBindings,
Pate.Verification.InlineCallee,
Pate.Verification.MemoryLog,
Pate.Verification.Override,
Expand Down
7 changes: 7 additions & 0 deletions src/Data/Parameterized/SetF.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ module Data.Parameterized.SetF
, fromSet
, map
, ppSetF
, asSet
) where

import Prelude hiding (filter, null, map)
import qualified Data.List as List
import Data.Parameterized.Classes
import qualified Data.Foldable as Foldable
import qualified Control.Lens as L

import qualified Prettyprinter as PP
import Prettyprinter ( (<+>) )
Expand Down Expand Up @@ -150,6 +152,11 @@ toSet (SetF s) = unsafeCoerce s
fromSet :: (OrdF f, Ord (f tp)) => Set (f tp) -> SetF f tp
fromSet s = SetF (unsafeCoerce s)

asSet ::
(OrdF f, Ord (f tp)) =>
L.Lens' (SetF f tp) (Set (f tp))
asSet f sf = fmap fromSet (f (toSet sf))

map ::
(OrdF g) => (f tp -> g tp) -> SetF f tp -> SetF g tp
map f (SetF s) = SetF (S.map (\(AsOrd v) -> AsOrd (f v)) s)
Expand Down
10 changes: 10 additions & 0 deletions src/Pate/AssumptionSet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ instance OrdF (W4.SymExpr sym) => PEM.ExprMappable sym (AssumptionSet sym) where
return $ MapF.singleton k' v'
return $ mkAssumptionSet sym ps' (foldr (mergeExprSetFMap (Proxy @sym)) MapF.empty bs')

instance PEM.ExprFoldable sym (AssumptionSet sym) where
foldExpr sym f (AssumptionSet ps bs) acc =
PEM.withSymExprFoldable @W4.BaseBoolType sym $
PEM.foldExpr sym f ps acc >>= PEM.foldExpr sym f bs

instance forall sym. W4S.SerializableExprs sym => W4S.W4Serializable sym (AssumptionSet sym) where
w4Serialize (AssumptionSet ps bs) | SetF.null ps, MapF.null bs = W4S.w4Serialize True
w4Serialize (AssumptionSet ps bs) | [p] <- SetF.toList ps, MapF.null bs = W4S.w4SerializeF p
Expand Down Expand Up @@ -166,6 +171,11 @@ data NamedAsms sym (nm :: Symbol) =
instance W4S.SerializableExprs sym => W4S.W4Serializable sym (NamedAsms sym nm) where
w4Serialize (NamedAsms asm) = W4S.w4Serialize asm

instance PEM.ExprFoldable sym (NamedAsms sym nm) where
foldExpr sym f (NamedAsms asm) acc = PEM.foldExpr sym f asm acc

instance PEM.ExprFoldableF sym (NamedAsms sym)

instance PEM.ExprMappable sym (NamedAsms sym nm) where
mapExpr sym f (NamedAsms asm) = NamedAsms <$> PEM.mapExpr sym f asm

Expand Down
89 changes: 65 additions & 24 deletions src/Pate/Discovery/ParsedFunctions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,62 @@ import Data.List (isPrefixOf)

data ParsedBlocks arch = forall ids. ParsedBlocks [MD.ParsedBlock arch ids]

data CachedFunInfo arch (bin :: PBi.WhichBinary) = CachedFunInfo
{
cachedDfi :: Some (MD.DiscoveryFunInfo arch)
-- we take a snapshot of any state that affects macaw's code discovery so
-- we can check if the cached result is still valid on a hit
, cachedOverride :: Maybe (PB.AbsStateOverride arch)
-- FIXME: technically we can scope these to the current function body, but
-- these are added infrequently enough that it's probably not worth it
, cachedExtraJumps :: ExtraJumps arch
, cachedExtraTargets :: Set.Set (MM.ArchSegmentOff arch)
}

cachedFunInfo ::
forall arch bin ids.
ParsedFunctionMap arch bin ->
ParsedFunctionState arch bin ->
MD.DiscoveryFunInfo arch ids ->
CachedFunInfo arch bin
cachedFunInfo pfm st dfi = CachedFunInfo (Some dfi) (Map.lookup addr (absStateOverrides pfm)) (extraEdges st) (extraTargets st)
where
addr :: MM.ArchSegmentOff arch
addr = MD.discoveredFunAddr dfi

-- | Add a function entry to the cache, taking a snapshot of any relevant
-- state so we can validate the cached entry on retrieval
addFunctionEntry ::
ParsedFunctionMap arch bin ->
PB.FunctionEntry arch bin ->
MD.DiscoveryFunInfo arch ids ->
ParsedFunctionState arch bin ->
ParsedFunctionState arch bin
addFunctionEntry pfm fe dfi st = st { parsedFunctionCache = Map.insert fe (cachedFunInfo pfm st dfi) (parsedFunctionCache st ) }


-- | Fetch a cached discovery result for a function entry. Returns 'Nothing' if any relevant state in either the 'ParsedFunctionMap'
-- or the 'ParsedFunctionState' has changed since the entry was computed (i.e. the cached entry is potentially invalid and
-- needs to be re-computed).
lookupFunctionEntry ::
MM.ArchConstraints arch =>
ParsedFunctionMap arch bin ->
ParsedFunctionState arch bin ->
PB.FunctionEntry arch bin ->
Maybe (Some (MD.DiscoveryFunInfo arch))
lookupFunctionEntry pfm st fe = case Map.lookup fe (parsedFunctionCache st) of
Just cachedInfo |
Some dfi <- cachedDfi cachedInfo
, mov <- Map.lookup (MD.discoveredFunAddr dfi) (absStateOverrides pfm)
, mov == cachedOverride cachedInfo
, extraEdges st == cachedExtraJumps cachedInfo
, extraTargets st == cachedExtraTargets cachedInfo
-> Just (Some dfi)
_ -> Nothing


data ParsedFunctionState arch bin =
ParsedFunctionState { parsedFunctionCache :: Map.Map (PB.FunctionEntry arch bin) (Some (MD.DiscoveryFunInfo arch))
ParsedFunctionState { parsedFunctionCache :: Map.Map (PB.FunctionEntry arch bin) (CachedFunInfo arch bin)
, discoveryState :: MD.DiscoveryState arch
, extraTargets :: Set.Set (MM.ArchSegmentOff arch)
, extraEdges :: ExtraJumps arch
Expand Down Expand Up @@ -132,7 +186,6 @@ addExtraTarget pfm tgt = do
False -> do
IORef.modifyIORef' (parsedStateRef pfm) $ \st' ->
st' { extraTargets = Set.insert tgt (extraTargets st')}
flushCache pfm

getExtraTargets ::
ParsedFunctionMap arch bin ->
Expand All @@ -141,16 +194,6 @@ getExtraTargets pfm = do
st <- IORef.readIORef (parsedStateRef pfm)
return $ extraTargets st

flushCache ::
MM.ArchConstraints arch =>
ParsedFunctionMap arch bin ->
IO ()
flushCache pfm = do
st <- IORef.readIORef (parsedStateRef pfm)
let ainfo = MD.archInfo (discoveryState st)
IORef.modifyIORef' (parsedStateRef pfm) $ \st' ->
st' { parsedFunctionCache = mempty, discoveryState = initDiscoveryState pfm ainfo }

isUnsupportedErr :: T.Text -> Bool
isUnsupportedErr err =
T.isPrefixOf "UnsupportedInstruction" err
Expand Down Expand Up @@ -231,16 +274,12 @@ addExtraEdges ::
addExtraEdges pfm es = do
mapM_ addTgt (Map.elems es)
IORef.modifyIORef' (parsedStateRef pfm) $ \st' ->
st' { extraEdges = Map.merge Map.preserveMissing Map.preserveMissing (Map.zipWithMaybeMatched (\_ l r -> Just (l <> r))) es (extraEdges st')}
st' { extraEdges = Map.merge Map.preserveMissing Map.preserveMissing (Map.zipWithMaybeMatched (\_ l r -> Just (l <> r))) es (extraEdges st') }
where
addTgt :: ExtraJumpTarget arch -> IO ()
addTgt = \case
DirectTargets es' -> mapM_ (addExtraTarget pfm) (Set.toList es')
-- we need to flush the cache here to ensure that we re-check the block at the
-- call site(s) after adding this as a return
ReturnTarget -> flushCache pfm
-- a call shouldn't require special treatment since it won't introduce
-- any edges
ReturnTarget -> return ()
DirectCall{} -> return ()

-- | Apply the various overrides to the architecture definition before returning the discovery state
Expand Down Expand Up @@ -518,7 +557,7 @@ parsedFunctionContaining blk pfm@(ParsedFunctionMap pfmRef mCFGDir _pd _ _ _ _ _
st <- getParsedFunctionState faddr pfm

-- First, check if we have a cached set of blocks for this state
case Map.lookup (PB.blockFunctionEntry blk) (parsedFunctionCache st) of
case lookupFunctionEntry pfm st (PB.blockFunctionEntry blk) of
Just sdfi -> return (Just sdfi)
Nothing -> do
-- Otherwise, run code discovery at this address
Expand All @@ -531,7 +570,10 @@ parsedFunctionContaining blk pfm@(ParsedFunctionMap pfmRef mCFGDir _pd _ _ _ _ _
-- IORef that might be evaluated multiple times if there is a lot of
-- contention. If that becomes a problem, we may want to change this
-- to an MVar where we fully evaluate each result before updating it.
(_, Some dfi) <- atomicAnalysis faddr st
(st', Some dfi) <- atomicAnalysis faddr st
IORef.modifyIORef pfmRef $ \st_ ->
st_ { parsedFunctionCache = parsedFunctionCache st' }

--IORef.writeIORef pfmRef pfm'
saveCFG mCFGDir (PB.blockBinRepr blk) dfi
return (Just (Some dfi))
Expand All @@ -557,11 +599,10 @@ parsedFunctionContaining blk pfm@(ParsedFunctionMap pfmRef mCFGDir _pd _ _ _ _ _
False -> do
let rsn = MD.CallTarget faddr
case incCompResult (MD.discoverFunction MD.defaultDiscoveryOptions faddr rsn (discoveryState st) []) of
(ds2, Some dfi) -> do
(_, Some dfi) -> do
entry <- resolveFunctionEntry (funInfoToFunEntry (PB.blockBinRepr blk) dfi pfm) pfm
return $ (st { parsedFunctionCache = Map.insert entry (Some dfi) (parsedFunctionCache st)
, discoveryState = ds2
}, Some dfi)
let st' = addFunctionEntry pfm entry dfi st
return $ (st', Some dfi)
bin :: PBi.WhichBinaryRepr bin
bin = PB.blockBinRepr blk

Expand Down
11 changes: 11 additions & 0 deletions src/Pate/Equivalence/Condition.hs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ instance PEM.ExprMappable sym (EquivalenceCondition sym arch v) where
<*> PEM.mapExpr sym f c
<*> PEM.mapExpr sym f d

instance PEM.ExprFoldableF sym (MM.ArchReg arch) => PEM.ExprFoldable sym (EquivalenceCondition sym arch v) where
foldExpr sym f (EquivalenceCondition a b c d) acc =
PEM.foldExpr sym f a acc
>>= PEM.foldExpr sym f b
>>= PEM.foldExpr sym f c
>>= PEM.foldExpr sym f d

instance PS.Scoped (EquivalenceCondition sym arch) where
unsafeCoerceScope (EquivalenceCondition a b c d) = EquivalenceCondition a (PS.unsafeCoerceScope b) c d

Expand Down Expand Up @@ -234,6 +241,10 @@ instance PS.Scoped (RegisterCondition sym arch) where
instance PEM.ExprMappable sym (RegisterCondition sym arch v) where
mapExpr sym f (RegisterCondition cond) = RegisterCondition <$> MM.traverseRegsWith (\_ -> PEM.mapExpr sym f) cond

instance PEM.ExprFoldableF sym (MM.ArchReg arch) => PEM.ExprFoldable sym (RegisterCondition sym arch v) where
foldExpr sym f (RegisterCondition cond) = PEM.foldExpr sym f (MM.regStateMap cond)


trueRegCond ::
W4.IsSymExprBuilder sym =>
PA.ValidArch arch =>
Expand Down
1 change: 1 addition & 0 deletions src/Pate/Equivalence/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ data InnerEquivalenceError arch
| forall e. X.Exception e => UnhandledException e
| IncompatibleSingletonNodes (PB.ConcreteBlock arch PBi.Original) (PB.ConcreteBlock arch PBi.Patched)
| SolverError X.SomeException
| ConcretizationFailure String

errShortName :: MS.SymArchConstraints arch => InnerEquivalenceError arch -> String
errShortName = \case
Expand Down
Loading