commit 2284937a6cab40688bf8f09a15b4062f6cef7e9e
parent 89244a3ad19b085993be5a2f0ca660964b8202b7
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 18 Oct 2026 07:14:07 +0400
diff: what every run executed, per object
Diffstat:
4 files changed, 44 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -13,7 +13,8 @@
with vestigia's plugin, its leakage trace summarised per
instruction, and where runs differing only in the secret
diverge (vestigia/diff-v1), set against controls repeating a
- secret; runs are recorded with randomisation off and the
- guest's entropy seeded.
+ secret, with what every run executed in each object; runs are
+ recorded with randomisation off and the guest's entropy
+ seeded.
* `traceOf` and `diffOf` run either as a derivation;
`nix/lib.nix` holds them for any package set.
diff --git a/README.md b/README.md
@@ -103,8 +103,10 @@ runs (a loop whose trip count the secret sets is one) or an
instruction run equally often whose addresses differ. Instructions
that merely ran a different number of times sit where control had
already diverged, and are counted as downstream rather than listed.
-`--object` keeps origins in objects whose path contains the given
-text.
+The report also lists, per object, the offset of every instruction
+some run executed: what the chosen secrets exercised, to set against
+what the code could execute. `--object` keeps origins, and what was
+executed, to objects whose path contains the given text.
A difference means the secret only if nothing else differs between
runs. `record` turns address-space randomisation off for itself and
diff --git a/lib/Vestigia/Diff.hs b/lib/Vestigia/Diff.hs
@@ -34,6 +34,10 @@
-- an instruction that differs between the first run and a control
-- differs without the secret (an entropy source, a clock) and is
-- `unsteady`, counted and excluded from the origins.
+--
+-- What every run executed, per object, is reported beside: the
+-- instructions the chosen secrets exercised, and so, against what
+-- a function could execute, the part of it the comparison saw.
module Vestigia.Diff (
Insn(..)
@@ -112,6 +116,8 @@ data Divergence = Divergence {
, dvUnsteady :: ![Insn]
, dvRuns :: !Int
, dvControls :: !Int
+ -- | the offsets some run, control or not, executed, by object
+ , dvExecuted :: !(M.Map T.Text (S.Set Integer))
} deriving (Eq, Show)
-- | Compare runs, uncontrolled.
@@ -134,6 +140,12 @@ diffControlled cs ds = Divergence {
, Just i <- [ firstInsn pc ] ]
, dvRuns = length ds
, dvControls = length cs
+ , dvExecuted = M.fromListWith S.union
+ [ (obj, S.singleton (inOffset i))
+ | d <- ds <> cs
+ , i <- M.elems (dpInsns d)
+ , inCount i > 0
+ , Just obj <- [ inObject i ] ]
}
where
-- what differs between the first run and a repeat of its secret
@@ -199,14 +211,19 @@ diffControlled cs ds = Divergence {
, Just i <- [ firstInsn pc ]
, inAccesses i > 0 ]
--- | Only the origins in objects whose path contains one of the
--- given texts; downstream instructions are kept, to be counted.
+-- | Only the origins, and what was executed, in objects whose path
+-- contains one of the given texts; downstream instructions are
+-- kept, to be counted.
restrictTo :: [T.Text] -> Divergence -> Divergence
restrictTo ps dv
| null ps = dv
- | otherwise = dv { dvOrigins = filter (inside . snd) (dvOrigins dv) }
+ | otherwise = dv {
+ dvOrigins = filter (inside . inObject . snd) (dvOrigins dv)
+ , dvExecuted = M.filterWithKey (\o _ -> inside (Just o))
+ (dvExecuted dv)
+ }
where
- inside i = case inObject i of
+ inside mo = case mo of
Just o -> any (`T.isInfixOf` o) ps
Nothing -> False
@@ -242,6 +259,9 @@ divergenceJson version places dv = Object
| (o, i) <- dvOrigins dv ])
, ("downstream", byObject (dvDownstream dv))
, ("unsteady", byObject (dvUnsteady dv))
+ , ("executed", Object
+ [ (o, Array (fmap (String . hex) (S.toAscList xs)))
+ | (o, xs) <- M.toList (dvExecuted dv) ])
]
where
count o = Number (toInteger (length
diff --git a/test/Main.hs b/test/Main.hs
@@ -6,6 +6,7 @@ module Main (
import qualified Data.ByteString as BS
import qualified Data.Map.Strict as M
+import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Test.Tasty
@@ -167,6 +168,18 @@ diffTests = testGroup "diff"
dvOrigins dv @?= []
dvDownstream dv @?= []
+ , testCase "what any run executed is kept per object" $ do
+ a <- either assertFailure pure (readDump dumpA)
+ b <- either assertFailure pure (readDump (T.unlines
+ [ "I 10 4 0 aaaa /lib.so 10", "I 50 1 0 eeee /lib.so 50"
+ , "I 60 1 0 ffff /libc.so 60", "I 70 0 0 0000 /lib.so 70" ]))
+ let dv = diffDumps [ a, b ]
+ dvExecuted dv @?= M.fromList
+ [ ("/lib.so", S.fromList [ 0x10, 0x20, 0x30, 0x40, 0x50 ])
+ , ("/libc.so", S.fromList [ 0x60 ]) ]
+ M.keys (dvExecuted (restrictTo [ "lib.so" ] dv))
+ @?= [ "/lib.so" ]
+
, testCase "an unreadable line is refused" $
isLeft (readDump "I 10 zz 0 f /lib.so 0\n") @? "accepted"
]