vestigia

Detect where execution depends on secrets.
git clone git://git.ppad.tech/vestigia.git
Log | Files | Refs | README | LICENSE

commit ea2106c74490e26a040f0f3a2bd040e78dd0df0c
parent d782f95f24aa25035b6f67ce3b7508ce4562886c
Author: Jared Tobin <jared@jtobin.io>
Date:   Tue, 20 Oct 2026 12:25:26 +0400

merge impl/objects: a taint run kept to the objects, the version exported

`vestigia run` and `read` take --object, keeping sites in objects
whose path contains the given text, as `diff` keeps its origins, and
traceOf takes `objects` as diffOf does. nix/lib.nix exports the
version the cabal file declares, so a consumer pinning vestigia
reads it rather than parsing the cabal file itself.

Diffstat:
MCHANGELOG | 6+++++-
MREADME.md | 13++++++++-----
Mapp/Main.hs | 9+++++----
Mflake.nix | 2+-
Mlib/Vestigia/Trace.hs | 13+++++++++++++
Mnix/lib.nix | 13+++++++++----
Mtest/Main.hs | 6++++++
7 files changed, 47 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -23,4 +23,8 @@ its getrandom included, seeded. A taint run that marked no secret is refused. * `traceOf` and `diffOf` run either as a derivation; - `nix/lib.nix` holds them for any package set. + `nix/lib.nix` holds them for any package set, with the version + the command reports. + * `--object` keeps a taint run's sites (`run`, `read`, and + `traceOf`'s `objects`) to objects whose path contains the given + text, as it keeps a differential run's origins. diff --git a/README.md b/README.md @@ -76,7 +76,9 @@ Here the modulus compares in decoding the secret scalar, its canonicality check, and the double-and-add of the scalar multiplication, which iterates about 245 times per call on the secret's bits. `vestigia read FILE.xml` gives the same report from a -memcheck run made elsewhere. +memcheck run made elsewhere. `--object TEXT` keeps the sites in +objects whose path contains the given text, as it keeps a +differential run's origins. ### Differential traces: `vestigia record` and `vestigia diff` @@ -142,10 +144,11 @@ origins. 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. `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. +its report is cached like any other output. Either takes `objects`, +the texts `--object` is given. `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, beside `version`, the version the command reports. ```nix vestigia.lib.x86_64-linux.diffOf { diff --git a/app/Main.hs b/app/Main.hs @@ -38,7 +38,7 @@ import Vestigia.Layout (fixLayout) import Vestigia.Memcheck (readMemcheck) import Vestigia.Symbol (symbolize) import Vestigia.Trace - (readObjects, trace, traceJson, traceSummary, withObjects) + (readObjects, sitesIn, trace, traceJson, traceSummary, withObjects) data Opts = Opts { oValgrind :: !(Maybe FilePath) @@ -82,8 +82,8 @@ usage e = do hPutStrLn stderr $ unlines [ "usage:" , " vestigia run [--valgrind PATH] [--xml FILE] [--out FILE] \ - \-- PROGRAM [ARG...]" - , " vestigia read [--out FILE] FILE.xml" + \[--object TEXT]... -- PROGRAM [ARG...]" + , " vestigia read [--out FILE] [--object TEXT]... FILE.xml" , " vestigia record [--qemu PATH] [--plugin PATH] --dump FILE \ \-- PROGRAM [ARG...]" , " vestigia diff [--out FILE] [--addr2line PATH] \ @@ -94,6 +94,7 @@ usage e = do , "branch and address that depended on bytes the program marked" , "secret (vestigia.h), and writes the report (vestigia/trace-v1)." , "valgrind is --valgrind, else $VESTIGIA_VALGRIND, else on PATH." + , "--object keeps sites in objects whose path contains TEXT." , "" , "record executes PROGRAM under QEMU with vestigia's plugin and" , "dumps its leakage trace per instruction; diff compares dumps" @@ -209,7 +210,7 @@ report o exit os xml = do hPutStrLn stderr ("vestigia: " <> xml <> ": " <> e) exitWith (ExitFailure 2) Right mc -> do - let tr = withObjects os (trace mc) + let tr = sitesIn (oObjects o) (withObjects os (trace mc)) doc = encode (traceJson (T.pack (showVersion version)) exit tr) maybe (TIO.putStr doc) (\p -> TIO.writeFile p doc) (oOut o) diff --git a/flake.nix b/flake.nix @@ -32,7 +32,7 @@ }; lib = { - inherit (v) traceOf diffOf valgrindDev; + inherit (v) traceOf diffOf valgrindDev version; }; checks.default = hpkgs.vestigia; diff --git a/lib/Vestigia/Trace.hs b/lib/Vestigia/Trace.hs @@ -22,6 +22,7 @@ module Vestigia.Trace ( , Site(..) , Trace(..) , trace + , sitesIn , withObjects , readObjects , objectOffset @@ -118,6 +119,18 @@ trace mc = Trace { grouped = M.fromListWith (flip (<>)) [ ((c, frIp top), [ (top, e) ]) | ((c, top), e) <- tagged ] +-- | Only the sites in objects whose path contains one of the given +-- texts, as a differential run keeps its origins; every site when +-- none is given. +sitesIn :: [T.Text] -> Trace -> Trace +sitesIn ts tr + | null ts = tr + | otherwise = tr { trSites = filter inside (trSites tr) } + where + inside s = case frObj (siFrame s) of + Just o -> any (`T.isInfixOf` o) ts + Nothing -> False + -- | The trace with the harness's map of its objects, by which each -- frame is named by its address in its object. withObjects :: [(Integer, T.Text)] -> Trace -> Trace diff --git a/nix/lib.nix b/nix/lib.nix @@ -72,17 +72,19 @@ let ''; # one harness run under memcheck, as a derivation: its report, - # memcheck's XML, and what the harness printed. A harness that + # memcheck's XML, and what the harness printed. `objects` keeps + # sites in objects whose path contains one of them. A harness that # fails still leaves a report of what ran; a run vestigia could # not read fails the build. - traceOf = { name, harness, args ? [ ] }: + traceOf = { name, harness, args ? [ ], objects ? [ ] }: 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} \ + ${lib.concatMapStrings (o: "--object ${lib.escapeShellArg o} ") + objects}-- ${harness} ${lib.escapeShellArgs args} \ > $out/stdout 2> $out/stderr echo $? > $out/status set -e @@ -139,8 +141,11 @@ let runs}2> $out/summary echo $(( ($(date +%s%N) - start) / 1000000 )) > $out/milliseconds ''; + # the version the cabal file declares, which the command reports + version = builtins.head (builtins.match ".*\nversion: *([0-9.]+).*" + (builtins.readFile (src + "/vestigia.cabal"))); in { - inherit hpkgs vestigia plugin include traceOf diffOf; + inherit hpkgs vestigia plugin include traceOf diffOf version; valgrindDev = valgrind.dev; } diff --git a/test/Main.hs b/test/Main.hs @@ -108,6 +108,12 @@ traceTests = testGroup "trace" @?= Just 0x401000 objectOffset os (f "/lib/libc.so.6" 0x7000) @?= Nothing + , testCase "a site is kept to the objects named" $ do + tr <- trace <$> fixture "probe.xml" + length (trSites (sitesIn [ "probe" ] tr)) @?= 2 + trSites (sitesIn [ "libc" ] tr) @?= [] + length (trSites (sitesIn [] tr)) @?= 2 + , testCase "one site per instruction, whatever called it" $ do tr <- trace <$> either assertFailure pure (readMemcheck twoCallers) fmap (\s -> (frIp (siFrame s), siCount s, length (siStacks s)))