commit 7440899be79afbd1839f87c905c68515643be54f
parent f87fdf896e5a6dda1ed834261a44b398bc60cfa6
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 17 Oct 2026 22:23:36 +0400
diff: controls, seeded entropy, and nix/lib.nix for consumers
Diffstat:
11 files changed, 285 insertions(+), 146 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -11,5 +11,8 @@
* `vestigia record` and `vestigia diff`: the harness under QEMU
with vestigia's plugin, its leakage trace summarised per
instruction, and where runs differing only in the secret
- diverge (vestigia/diff-v1).
- * `traceOf` and `diffOf` run either as a derivation.
+ diverge (vestigia/diff-v1), set against controls repeating a
+ secret; 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
@@ -85,12 +85,15 @@ in the secret, and `diff` reports where the runs diverge:
$ for s in 1 2 3; do
> vestigia record --dump run-$s -- ./harness public_key 2 $s
> done
-$ vestigia diff --object libpenumbra_audit --out diff.json run-*
+$ vestigia record --dump control -- ./harness public_key 2 1
+$ vestigia diff --object libpenumbra_audit --control control \
+> --out diff.json run-*
control 0xe9d6 Element::vartime_compress_to_field sign.rs:11 ...
address 0x152c8 Fq::sqrt_ratio_zeta invsqrt.rs:113 ...
address 0x152eb Fq::sqrt_ratio_zeta invsqrt.rs:117 ...
...
-44 origin(s) over 3 run(s); 1383 instruction(s) downstream
+44 origin(s) over 3 run(s); 1383 instruction(s) downstream; 0 unsteady
+over 1 control(s)
```
An *origin* is a branch whose outcomes differ in proportion between
@@ -99,15 +102,26 @@ 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. `record` turns address-space randomisation off for itself and
-QEMU, since runs to be compared must lay memory out alike.
+text.
+
+A difference means the secret only if nothing else differs between
+runs. `record` turns address-space randomisation off for itself and
+QEMU, and seeds the guest's entropy, so runs lay memory out alike
+and a hash table seeded from the system's randomness is laid out
+alike too. `--control DUMP` names a run repeating the first run's
+secret: whatever differs between the two (a clock read, a
+scheduler's choice) is *unsteady*, counted and kept out of the
+origins.
### As derivations
The flake's `lib.<system>` holds `traceOf` and `diffOf`, which run a
harness under either instrument as a derivation: a run for
`x86_64-linux` then executes on whatever builds for that system, and
-its report is cached like any other output.
+its report is cached like any other output. `diffOf` repeats the
+first run as its control. `nix/lib.nix` holds the same definitions
+for one package set, for a consumer that pins vestigia and builds
+traces with its own nixpkgs.
```nix
vestigia.lib.x86_64-linux.diffOf {
diff --git a/app/Main.hs b/app/Main.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
@@ -15,8 +14,7 @@ module Main (
) where
import Control.Exception (IOException, try)
-import Control.Monad (when)
-import Foreign.C.Types (CInt(..))
+import Control.Monad (unless)
import qualified Data.ByteString as BS
import Data.Version (showVersion)
import qualified Data.Text as T
@@ -31,18 +29,14 @@ import System.Process (proc, waitForProcess, withCreateProcess)
import Paths_vestigia (version)
import Vestigia.Diff
- (diffDumps, divergenceJson, divergenceSummary, dvOrigins, inObject,
- inOffset, readDump, restrictTo)
+ (diffControlled, divergenceJson, divergenceSummary, dvOrigins,
+ inObject, inOffset, readDump, restrictTo)
import Vestigia.Json (encode)
+import Vestigia.Layout (fixLayout)
import Vestigia.Memcheck (readMemcheck)
import Vestigia.Symbol (symbolize)
import Vestigia.Trace (trace, traceJson, traceSummary)
--- address-space randomisation off, for this process and its
--- children; nonzero where it cannot be (cbits/norandom.c)
-foreign import ccall unsafe "vestigia_no_randomize"
- c_noRandomize :: IO CInt
-
data Opts = Opts {
oValgrind :: !(Maybe FilePath)
, oXml :: !(Maybe FilePath)
@@ -52,6 +46,7 @@ data Opts = Opts {
, oDump :: !(Maybe FilePath)
, oAddr2line :: !(Maybe FilePath)
, oObjects :: ![T.Text]
+ , oControls :: ![FilePath]
}
main :: IO ()
@@ -88,7 +83,7 @@ usage e = do
, " vestigia record [--qemu PATH] [--plugin PATH] --dump FILE \
\-- PROGRAM [ARG...]"
, " vestigia diff [--out FILE] [--addr2line PATH] \
- \[--object TEXT]... DUMP DUMP..."
+ \[--object TEXT]... [--control DUMP]... DUMP DUMP..."
, ""
, "run executes PROGRAM under memcheck, which reports every"
, "branch and address that depended on bytes the program marked"
@@ -99,7 +94,9 @@ usage e = do
, "dumps its leakage trace per instruction; diff compares dumps"
, "of runs that differ only in a secret and reports where they"
, "diverge (vestigia/diff-v1), naming sources with addr2line."
- , "--object keeps origins in objects whose path contains TEXT."
+ , "--object keeps origins in objects whose path contains TEXT;"
+ , "--control names a run repeating the first run's secret, and"
+ , "what differs in it is unsteady, not an origin."
, "qemu, the plugin, and addr2line default to $VESTIGIA_QEMU,"
, "$VESTIGIA_PLUGIN, and $VESTIGIA_ADDR2LINE."
]
@@ -108,7 +105,7 @@ usage e = do
-- options before the program; "--" ends them
options :: [String] -> Either String (Opts, [String])
options = go (Opts Nothing Nothing Nothing Nothing Nothing Nothing
- Nothing [])
+ Nothing [] [])
where
go o xs = case xs of
("--" : rest) -> Right (o, rest)
@@ -121,6 +118,8 @@ options = go (Opts Nothing Nothing Nothing Nothing Nothing Nothing
("--addr2line" : p : rest) -> go o { oAddr2line = Just p } rest
("--object" : t : rest) ->
go o { oObjects = oObjects o <> [ T.pack t ] } rest
+ ("--control" : p : rest) ->
+ go o { oControls = oControls o <> [ p ] } rest
(x : _) | take 2 x == "--" -> Left ("unknown option: " <> x)
_ -> Right (o, xs)
@@ -196,15 +195,18 @@ tool opt var dflt = do
record :: Opts -> FilePath -> [String] -> IO ()
record o prog progArgs = do
dump <- maybe (usage "record: --dump FILE is needed") pure (oDump o)
- -- runs to be compared must lay memory out alike
- fixed <- c_noRandomize
- when (fixed /= 0) $ hPutStrLn stderr
+ -- runs to be compared must lay memory out alike, and draw the
+ -- same entropy: a hash table seeded from the operating system's
+ -- randomness is laid out anew in every process
+ fixed <- fixLayout
+ unless fixed $ hPutStrLn stderr
"vestigia: address randomisation is still on; runs may differ \
\in layout, and so everywhere"
qemu <- tool (oQemu o) "VESTIGIA_QEMU" "qemu-x86_64"
plugin <- tool (oPlugin o) "VESTIGIA_PLUGIN" "libvestigia-qemu.so"
r <- try (withCreateProcess
- (proc qemu ([ "-plugin", plugin <> ",out=" <> dump, prog ]
+ (proc qemu ([ "-seed", "0"
+ , "-plugin", plugin <> ",out=" <> dump, prog ]
<> progArgs))
(\_ _ _ ph -> waitForProcess ph))
case r of
@@ -218,7 +220,8 @@ diff :: Opts -> [FilePath] -> IO ()
diff o paths = do
addr2line <- tool (oAddr2line o) "VESTIGIA_ADDR2LINE" "addr2line"
dumps <- traverse readOne paths
- let dv = restrictTo (oObjects o) (diffDumps dumps)
+ controls <- traverse readOne (oControls o)
+ let dv = restrictTo (oObjects o) (diffControlled controls dumps)
places <- symbolize addr2line
[ (obj, inOffset i) | (_, i) <- dvOrigins dv
, Just obj <- [ inObject i ] ]
diff --git a/etc/spike/penumbra/RESULTS.md b/etc/spike/penumbra/RESULTS.md
@@ -34,7 +34,10 @@ address):
check.
Differential traces (44, 45, and 72 origins; about 1380
-instructions downstream):
+instructions downstream; with the guest's entropy seeded and a
+control repeating the first secret, none unsteady, so the
+`invsqrt` lookups are the secret's and not hashbrown's random
+seeding):
- The double-and-add (bits.rs:25, 32; skip_while.rs:50).
- The modulus compares inside every field operation on the point's
diff --git a/etc/spike/poly1305/RESULTS.md b/etc/spike/poly1305/RESULTS.md
@@ -44,6 +44,9 @@ per three-run diff, on pollux.
differential runs.
- Address-space randomisation made identical runs differ at 2183
places; `vestigia record` now turns it off for itself and QEMU.
-- A seed passed as an argument is decoded by libc, which shows as
- origins in libc; differential origins are filtered to the
- harness binary, which holds the whole Haskell program.
+- Even with layout fixed, a GHC program does not run identically
+ twice: about 120 instructions in libc and the runtime differ
+ between runs of the same key. A control run repeating the first
+ key exposes them, and `diff` sets them aside as unsteady; with
+ the control, mutants-1's origins are L3's address and L1's
+ branches, and none of the runtime's.
diff --git a/flake.nix b/flake.nix
@@ -16,115 +16,17 @@
let
pkgs = import nixpkgs { inherit system; };
lib = pkgs.lib;
-
- hpkgs = pkgs.haskell.packages.ghc910.extend (new: old: {
- vestigia = new.callCabal2nix "vestigia" ./. { };
- });
-
- # memcheck runs on Linux only; elsewhere the command can still
- # read a run's XML
+ v = import ./nix/lib.nix { inherit pkgs; src = ./.; };
+ hpkgs = v.hpkgs;
linux = pkgs.stdenv.hostPlatform.isLinux;
- valgrind = pkgs.valgrind;
-
- # the QEMU plugin recording a run's leakage trace per
- # instruction, and the QEMU that runs a harness of this
- # system's ISA under it
- qemu = pkgs.qemu;
- qemuFor = {
- x86_64 = "${qemu}/bin/qemu-x86_64";
- aarch64 = "${qemu}/bin/qemu-aarch64";
- };
- plugin = pkgs.runCommandCC "vestigia-qemu-plugin" {
- nativeBuildInputs = [ pkgs.pkg-config ];
- buildInputs = [ pkgs.glib ];
- } ''
- mkdir -p $out/lib
- cc -O2 -shared -fPIC -Wall -Werror \
- $(pkg-config --cflags glib-2.0) -I${qemu}/include \
- -o $out/lib/libvestigia-qemu.so ${./plugin/vestigia-qemu.c}
- '';
- isa = builtins.head (lib.splitString "-" system);
-
- # the harness-side header; a harness build also needs
- # valgrind's own (valgrindDev)
- include = pkgs.runCommand "vestigia-include" { } ''
- mkdir -p $out/include
- cp ${./include/vestigia.h} $out/include/vestigia.h
- cp ${./include/vestigia-ffi.c} $out/include/vestigia-ffi.c
- '';
-
- # the command, with the valgrind it was built against baked in
- vestigia = pkgs.runCommand "vestigia" {
- nativeBuildInputs = [ pkgs.makeWrapper ];
- } ''
- mkdir -p $out/bin
- makeWrapper ${hpkgs.vestigia}/bin/vestigia $out/bin/vestigia \
- ${lib.optionalString linux (lib.concatStringsSep " " [
- "--set-default VESTIGIA_VALGRIND ${valgrind}/bin/valgrind"
- "--set-default VESTIGIA_QEMU ${qemuFor.${isa}}"
- "--set-default VESTIGIA_PLUGIN"
- "${plugin}/lib/libvestigia-qemu.so"
- "--set-default VESTIGIA_ADDR2LINE"
- "${pkgs.binutils-unwrapped}/bin/addr2line"
- ])}
- ln -s ${include}/include $out/include
- '';
-
- # one harness run under memcheck, as a derivation: its report,
- # memcheck's XML, and what the harness printed. A harness that
- # fails still leaves a report of what ran; a run vestigia could
- # not read fails the build.
- traceOf = { name, harness, args ? [ ] }:
- pkgs.runCommand "vestigia-trace-${name}" { } ''
- mkdir -p $out
- start=$(date +%s%N)
- set +e
- ${vestigia}/bin/vestigia run \
- --xml $out/memcheck.xml --out $out/trace.json \
- -- ${harness} ${lib.escapeShellArgs args} \
- > $out/stdout 2> $out/stderr
- echo $? > $out/status
- set -e
- echo $(( ($(date +%s%N) - start) / 1000000 )) > $out/milliseconds
- test -s $out/trace.json || {
- cat $out/stderr >&2
- exit 1
- }
- '';
-
- # runs of one harness that differ only in a secret, each
- # recorded under QEMU, and where they diverge: the dumps, and
- # the report. `runs` is a list of argument lists; `objects`
- # keeps origins in objects whose path contains one of them.
- # A run's own failure leaves its status; a dump vestigia
- # cannot read fails the build.
- diffOf = { name, harness, runs, objects ? [ ] }:
- pkgs.runCommand "vestigia-diff-${name}" { } ''
- mkdir -p $out
- start=$(date +%s%N)
- ${lib.concatImapStrings (k: args: ''
- set +e
- ${vestigia}/bin/vestigia record --dump $out/dump-${toString k} \
- -- ${harness} ${lib.escapeShellArgs args} \
- > $out/stdout-${toString k} 2> $out/stderr-${toString k}
- echo $? > $out/status-${toString k}
- set -e
- '') runs}
- ${vestigia}/bin/vestigia diff --out $out/diff.json \
- ${lib.concatMapStrings (o: "--object ${lib.escapeShellArg o} ")
- objects} \
- $out/dump-* 2> $out/summary
- echo $(( ($(date +%s%N) - start) / 1000000 )) > $out/milliseconds
- '';
in
{
- packages.default = vestigia;
- packages.include = include;
- packages.plugin = plugin;
+ packages.default = v.vestigia;
+ packages.include = v.include;
+ packages.plugin = v.plugin;
lib = {
- inherit traceOf diffOf;
- valgrindDev = valgrind.dev;
+ inherit (v) traceOf diffOf valgrindDev;
};
checks.default = hpkgs.vestigia;
@@ -134,7 +36,7 @@
buildInputs = [
hpkgs.cabal-install
pkgs.stdenv.cc
- ] ++ lib.optional linux valgrind;
+ ] ++ lib.optional linux pkgs.valgrind;
shellHook = ''
PS1="[vestigia] \w$ "
diff --git a/lib/Vestigia/Diff.hs b/lib/Vestigia/Diff.hs
@@ -28,6 +28,12 @@
-- branch and then used as an index differs between runs, though
-- no undefined bit ever reached it. It sees only what the chosen
-- secrets actually varied.
+--
+-- A difference means the secret only if nothing else differs
+-- between runs. Controls are runs repeating the first run's secret:
+-- 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.
module Vestigia.Diff (
Insn(..)
@@ -36,6 +42,7 @@ module Vestigia.Diff (
, Origin(..)
, Divergence(..)
, diffDumps
+ , diffControlled
, restrictTo
, divergenceJson
, divergenceSummary
@@ -102,19 +109,48 @@ data Origin =
data Divergence = Divergence {
dvOrigins :: ![(Origin, Insn)]
, dvDownstream :: ![Insn]
+ , dvUnsteady :: ![Insn]
, dvRuns :: !Int
+ , dvControls :: !Int
} deriving (Eq, Show)
--- | Compare runs. Every run is set against every other, so an
--- origin is any instruction at which some two runs differ.
+-- | Compare runs, uncontrolled.
diffDumps :: [Dump] -> Divergence
-diffDumps ds = Divergence {
- dvOrigins = L.sortOn (inPc . snd) (controls <> addresses)
+diffDumps = diffControlled []
+
+-- | Compare runs, given controls repeating the first run's secret.
+-- Every run is set against every other, so an origin is any
+-- instruction at which some two runs differ, and which no control
+-- shows differing without the secret.
+diffControlled :: [Dump] -> [Dump] -> Divergence
+diffControlled cs ds = Divergence {
+ dvOrigins = L.sortOn (inPc . snd)
+ [ o | o@(_, i) <- controls <> addresses
+ , not (inPc i `S.member` unsteady) ]
, dvDownstream = [ i | pc <- S.toAscList downstream
+ , not (pc `S.member` unsteady)
, Just i <- [ firstInsn pc ] ]
+ , dvUnsteady = [ i | pc <- S.toAscList unsteady
+ , Just i <- [ firstInsn pc ] ]
, dvRuns = length ds
+ , dvControls = length cs
}
where
+ -- what differs between the first run and a repeat of its secret
+ unsteady = case ds of
+ (d : _) -> S.unions (fmap (differing d) cs)
+ [] -> S.empty
+ differing a b = S.fromList
+ [ pc
+ | pc <- S.toList (M.keysSet (dpInsns a)
+ `S.union` M.keysSet (dpInsns b))
+ , let ia = M.lookup pc (dpInsns a)
+ ib = M.lookup pc (dpInsns b)
+ ta = M.lookup pc (dpTransfers a)
+ tb = M.lookup pc (dpTransfers b)
+ , fmap sig ia /= fmap sig ib || ta /= tb ]
+ sig i = (inCount i, inFingerprint i)
+
pcs = S.unions (fmap (M.keysSet . dpInsns) ds)
firstInsn pc = case [ i | d <- ds
, Just i <- [ M.lookup pc (dpInsns d) ] ] of
@@ -187,10 +223,12 @@ divergenceJson version places dv = Object
, ("tool", Object [ ("name", String "vestigia")
, ("version", String version) ])
, ("runs", Number (toInteger (dvRuns dv)))
+ , ("controls", Number (toInteger (dvControls dv)))
, ("summary", Object
[ ("control", count ControlOrigin)
, ("address", count AddressOrigin)
, ("downstream", Number (toInteger (length (dvDownstream dv))))
+ , ("unsteady", Number (toInteger (length (dvUnsteady dv))))
])
, ("origins", Array
[ Object $
@@ -202,15 +240,16 @@ divergenceJson version places dv = Object
, ("frames", Array (fmap placeJson (placesOf i)))
]
| (o, i) <- dvOrigins dv ])
- , ("downstream", Object
- [ (o, Number (toInteger n))
- | (o, n) <- M.toList (M.fromListWith (+)
- [ (maybe "-" id (inObject i), 1 :: Int)
- | i <- dvDownstream dv ]) ])
+ , ("downstream", byObject (dvDownstream dv))
+ , ("unsteady", byObject (dvUnsteady dv))
]
where
count o = Number (toInteger (length
[ () | (o', _) <- dvOrigins dv, o' == o ]))
+ byObject is = Object
+ [ (o, Number (toInteger n))
+ | (o, n) <- M.toList (M.fromListWith (+)
+ [ (maybe "-" id (inObject i), 1 :: Int) | i <- is ]) ]
placesOf i = case inObject i of
Just obj -> M.findWithDefault [] (obj, inOffset i) places
Nothing -> []
@@ -234,7 +273,10 @@ divergenceSummary places dv = T.unlines $
<> [ T.pack (show (length (dvOrigins dv))) <> " origin(s) over "
<> T.pack (show (dvRuns dv)) <> " run(s); "
<> T.pack (show (length (dvDownstream dv)))
- <> " instruction(s) downstream" ]
+ <> " instruction(s) downstream; "
+ <> T.pack (show (length (dvUnsteady dv)))
+ <> " unsteady over " <> T.pack (show (dvControls dv))
+ <> " control(s)" ]
where
innermost i = case inObject i of
Just obj -> case M.findWithDefault [] (obj, inOffset i) places of
diff --git a/lib/Vestigia/Layout.hs b/lib/Vestigia/Layout.hs
@@ -0,0 +1,27 @@
+{-# OPTIONS_HADDOCK prune #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+-- |
+-- Module: Vestigia.Layout
+-- Copyright: (c) 2026 Jared Tobin
+-- License: MIT
+-- Maintainer: Jared Tobin <jared@ppad.tech>
+--
+-- Runs compared to find where a secret mattered must lay memory out
+-- alike, or every address differs between them and no difference
+-- means anything.
+
+module Vestigia.Layout (
+ fixLayout
+ ) where
+
+import Foreign.C.Types (CInt(..))
+
+foreign import ccall unsafe "vestigia_no_randomize"
+ c_noRandomize :: IO CInt
+
+-- | Turn address-space randomisation off for this process and every
+-- process it starts (the personality is inherited across fork and
+-- exec). 'False' where it cannot be: off Linux, or refused.
+fixLayout :: IO Bool
+fixLayout = fmap (== 0) c_noRandomize
diff --git a/nix/lib.nix b/nix/lib.nix
@@ -0,0 +1,127 @@
+# vestigia for one system: the command (with the valgrind, QEMU,
+# plugin, and addr2line it runs baked in where they exist), the QEMU
+# plugin, the harness-side headers, and the functions that run a
+# harness under either instrument as a derivation.
+#
+# The flake applies it per system; a consumer pinning vestigia (as
+# synthesis does) imports it with its own package set, so a trace is
+# built by the same definitions wherever it is built.
+{ pkgs, src ? ../. }:
+
+let
+ lib = pkgs.lib;
+
+ hpkgs = pkgs.haskell.packages.ghc910.extend (new: old: {
+ vestigia = new.callCabal2nix "vestigia" src { };
+ });
+
+ # memcheck runs on Linux only; elsewhere the command can still
+ # read a run's XML
+ linux = pkgs.stdenv.hostPlatform.isLinux;
+ valgrind = pkgs.valgrind;
+
+ # the QEMU plugin recording a run's leakage trace per
+ # instruction, and the QEMU that runs a harness of this
+ # system's ISA under it
+ qemu = pkgs.qemu;
+ qemuFor = {
+ x86_64 = "${qemu}/bin/qemu-x86_64";
+ aarch64 = "${qemu}/bin/qemu-aarch64";
+ };
+ plugin = pkgs.runCommandCC "vestigia-qemu-plugin" {
+ nativeBuildInputs = [ pkgs.pkg-config ];
+ buildInputs = [ pkgs.glib ];
+ } ''
+ mkdir -p $out/lib
+ cc -O2 -shared -fPIC -Wall -Werror \
+ $(pkg-config --cflags glib-2.0) -I${qemu}/include \
+ -o $out/lib/libvestigia-qemu.so ${src}/plugin/vestigia-qemu.c
+ '';
+ isa = builtins.head
+ (lib.splitString "-" pkgs.stdenv.hostPlatform.system);
+
+ # the harness-side header; a harness build also needs
+ # valgrind's own (valgrindDev)
+ include = pkgs.runCommand "vestigia-include" { } ''
+ mkdir -p $out/include
+ cp ${src}/include/vestigia.h $out/include/vestigia.h
+ cp ${src}/include/vestigia-ffi.c $out/include/vestigia-ffi.c
+ '';
+
+ # the command, with the valgrind it was built against baked in
+ vestigia = pkgs.runCommand "vestigia" {
+ nativeBuildInputs = [ pkgs.makeWrapper ];
+ } ''
+ mkdir -p $out/bin
+ makeWrapper ${hpkgs.vestigia}/bin/vestigia $out/bin/vestigia \
+ ${lib.optionalString linux (lib.concatStringsSep " " [
+ "--set-default VESTIGIA_VALGRIND ${valgrind}/bin/valgrind"
+ "--set-default VESTIGIA_QEMU ${qemuFor.${isa}}"
+ "--set-default VESTIGIA_PLUGIN"
+ "${plugin}/lib/libvestigia-qemu.so"
+ "--set-default VESTIGIA_ADDR2LINE"
+ "${pkgs.binutils-unwrapped}/bin/addr2line"
+ ])}
+ ln -s ${include}/include $out/include
+ '';
+
+ # one harness run under memcheck, as a derivation: its report,
+ # memcheck's XML, and what the harness printed. A harness that
+ # fails still leaves a report of what ran; a run vestigia could
+ # not read fails the build.
+ traceOf = { name, harness, args ? [ ] }:
+ pkgs.runCommand "vestigia-trace-${name}" { } ''
+ mkdir -p $out
+ start=$(date +%s%N)
+ set +e
+ ${vestigia}/bin/vestigia run \
+ --xml $out/memcheck.xml --out $out/trace.json \
+ -- ${harness} ${lib.escapeShellArgs args} \
+ > $out/stdout 2> $out/stderr
+ echo $? > $out/status
+ set -e
+ echo $(( ($(date +%s%N) - start) / 1000000 )) > $out/milliseconds
+ test -s $out/trace.json || {
+ cat $out/stderr >&2
+ exit 1
+ }
+ '';
+
+ # runs of one harness that differ only in a secret, each
+ # recorded under QEMU, and where they diverge: the dumps, and
+ # the report. `runs` is a list of argument lists; `objects`
+ # keeps origins in objects whose path contains one of them. The
+ # first run is repeated as a control, so that what differs
+ # without the secret is set aside rather than taken for it.
+ # A run's own failure leaves its status; a dump vestigia
+ # cannot read fails the build.
+ diffOf = { name, harness, runs, objects ? [ ] }:
+ pkgs.runCommand "vestigia-diff-${name}" { } ''
+ mkdir -p $out
+ start=$(date +%s%N)
+ ${lib.concatImapStrings (k: args: ''
+ set +e
+ ${vestigia}/bin/vestigia record --dump $out/dump-${toString k} \
+ -- ${harness} ${lib.escapeShellArgs args} \
+ > $out/stdout-${toString k} 2> $out/stderr-${toString k}
+ echo $? > $out/status-${toString k}
+ set -e
+ '') runs}
+ set +e
+ ${vestigia}/bin/vestigia record --dump $out/control-1 \
+ -- ${harness} ${lib.escapeShellArgs (builtins.head runs)} \
+ > $out/stdout-control-1 2> $out/stderr-control-1
+ echo $? > $out/status-control-1
+ set -e
+ ${vestigia}/bin/vestigia diff --out $out/diff.json \
+ ${lib.concatMapStrings (o: "--object ${lib.escapeShellArg o} ")
+ objects} \
+ --control $out/control-1 \
+ $out/dump-* 2> $out/summary
+ echo $(( ($(date +%s%N) - start) / 1000000 )) > $out/milliseconds
+ '';
+in
+{
+ inherit hpkgs vestigia plugin include traceOf diffOf;
+ valgrindDev = valgrind.dev;
+}
diff --git a/test/Main.hs b/test/Main.hs
@@ -134,6 +134,20 @@ diffTests = testGroup "diff"
notElem 0x18 (fmap (inPc . snd) (dvOrigins dv))
@? "a branch that always jumps is not secret-dependent"
+ , testCase "what differs without the secret is unsteady" $ do
+ a <- either assertFailure pure (readDump dumpA)
+ b <- either assertFailure pure (readDump dumpB)
+ -- a repeat of A whose 0x20 touched other addresses: an
+ -- entropy source, not the secret
+ c <- either assertFailure pure (readDump (T.unlines
+ [ "I 10 4 0 aaaa /lib.so 10", "E 10 30 2"
+ , "I 20 4 4 ffff /lib.so 20", "I 30 2 0 cccc /lib.so 30"
+ , "I 40 1 1 dddd /lib.so 40" ]))
+ let dv = diffControlled [ c ] [ a, b ]
+ fmap (\(o, i) -> (o, inPc i)) (dvOrigins dv)
+ @?= [ (ControlOrigin, 0x10) ]
+ fmap inPc (dvUnsteady dv) @?= [ 0x20 ]
+
, testCase "identical runs have no origins" $ do
a <- either assertFailure pure (readDump dumpA)
let dv = diffDumps [ a, a ]
diff --git a/vestigia.cabal b/vestigia.cabal
@@ -26,9 +26,12 @@ library
hs-source-dirs: lib
ghc-options:
-Wall -Werror
+ c-sources:
+ cbits/norandom.c
exposed-modules:
Vestigia.Diff
, Vestigia.Json
+ , Vestigia.Layout
, Vestigia.Memcheck
, Vestigia.Symbol
, Vestigia.Trace
@@ -45,8 +48,6 @@ executable vestigia
main-is: Main.hs
ghc-options:
-Wall -Werror -rtsopts
- c-sources:
- cbits/norandom.c
other-modules:
Paths_vestigia
autogen-modules: