auditor

An aarch64 constant-time memory access auditing tool.
git clone git://git.ppad.tech/auditor.git
Log | Files | Refs | README | LICENSE

commit 85d6377d5d28913567bfb2226ab1c319717ca399
parent 490a29c43a37c1dfd2c81d8cbe7e163afc6f498e
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 13 Feb 2026 20:07:08 +0400

feat: add --callers/-c option for reverse reachability

Shows all symbols that can reach a target symbol (reverse of --symbol).
Uses graph transpose for efficient reverse traversal.

Usage:
  auditor -l -s SYMBOL -i file.s       # callees (default)
  auditor -l -s SYMBOL -c -i file.s    # callers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
Mapp/Main.hs | 50++++++++++++++++++++++++++++++++++++++++----------
Mlib/Audit/AArch64.hs | 2++
Mlib/Audit/AArch64/CallGraph.hs | 14+++++++++++++-
3 files changed, 55 insertions(+), 11 deletions(-)

diff --git a/app/Main.hs b/app/Main.hs @@ -8,7 +8,8 @@ import Audit.AArch64 , NctReason(..), NctFinding(..), nctLine, nctInstr, nctReason , LineMap, isGhcRuntimeFinding , SymbolScanResult(..) - , buildCallGraph, allSymbols + , buildCallGraph, allSymbols, reachableSymbols, reachingSymbols + , symbolExists , auditFile, auditFileInterProc , auditFileWithConfig, auditFileInterProcWithConfig , parseFile, regName, loadTaintConfig @@ -42,6 +43,7 @@ data Options = Options , optSymbol :: !(Maybe Text) , optListSymbols :: !Bool , optSymbolFilter :: !(Maybe Text) + , optCallers :: !Bool } deriving (Eq, Show) optParser :: Parser Options @@ -111,6 +113,11 @@ optParser = Options <> metavar "PATTERN" <> help "Filter symbols containing PATTERN (use with --list-symbols)" )) + <*> switch + ( long "callers" + <> short 'c' + <> help "Show callers instead of callees (use with --symbol)" + ) optInfo :: ParserInfo Options optInfo = info (optParser <**> helper) @@ -210,6 +217,7 @@ outputText opts ar = do else exitFailure -- | List all function symbols in the assembly file. +-- With --symbol, lists callees (or callers with --callers). listSymbols :: Options -> IO () listSymbols opts = do bs <- BS.readFile (optInput opts) @@ -224,15 +232,37 @@ listSymbols opts = do exitFailure Right lns -> do let cg = buildCallGraph lns - syms = Set.toAscList (allSymbols cg) - filtered = case optSymbolFilter opts of - Nothing -> syms - Just pat -> filter (T.isInfixOf pat) syms - mapM_ TIO.putStrLn filtered - if optQuiet opts - then pure () - else TIO.putStrLn $ "\n" <> T.pack (show (length filtered)) - <> " symbols" + case optSymbol opts of + Just sym + | not (symbolExists sym cg) -> do + TIO.putStrLn $ "Error: symbol not found: " <> sym + exitFailure + | optCallers opts -> do + -- Show symbols that can reach this symbol + let callers = Set.toAscList (reachingSymbols sym cg) + mapM_ TIO.putStrLn callers + if optQuiet opts + then pure () + else TIO.putStrLn $ "\n" <> T.pack (show (length callers)) + <> " symbols can reach " <> sym + | otherwise -> do + -- Show symbols reachable from this symbol + let callees = Set.toAscList (reachableSymbols sym cg) + mapM_ TIO.putStrLn callees + if optQuiet opts + then pure () + else TIO.putStrLn $ "\n" <> T.pack (show (length callees)) + <> " symbols reachable from " <> sym + Nothing -> do + let syms = Set.toAscList (allSymbols cg) + filtered = case optSymbolFilter opts of + Nothing -> syms + Just pat -> filter (T.isInfixOf pat) syms + mapM_ TIO.putStrLn filtered + if optQuiet opts + then pure () + else TIO.putStrLn $ "\n" <> T.pack (show (length filtered)) + <> " symbols" -- | Output NCT scan results for a specific symbol and its callees. outputNctSymbol :: Options -> SymbolScanResult -> IO () diff --git a/lib/Audit/AArch64.hs b/lib/Audit/AArch64.hs @@ -56,6 +56,7 @@ module Audit.AArch64 ( , buildCallGraph , allSymbols , reachableSymbols + , reachingSymbols , symbolExists -- * Results @@ -82,6 +83,7 @@ import Audit.AArch64.CallGraph , buildCallGraph , allSymbols , reachableSymbols + , reachingSymbols , symbolExists ) import qualified Audit.AArch64.CallGraph as CG (buildCallGraph) diff --git a/lib/Audit/AArch64/CallGraph.hs b/lib/Audit/AArch64/CallGraph.hs @@ -17,12 +17,13 @@ module Audit.AArch64.CallGraph ( -- * Queries , allSymbols , reachableSymbols + , reachingSymbols , symbolExists ) where import Audit.AArch64.CFG (isFunctionLabel) import Audit.AArch64.Types (Instr(..), Line(..)) -import Data.Graph (Graph, Vertex, graphFromEdges, reachable) +import Data.Graph (Graph, Vertex, graphFromEdges, reachable, transposeG) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Set (Set) @@ -87,6 +88,17 @@ reachableSymbols root cg = case cgVertexFromK cg root of [sym | v' <- reachable (cgGraph cg) v , let (_, sym, _) = cgNodeFromV cg v'] +-- | Get all symbols that can reach a target symbol (including the target). +-- +-- This is the reverse of 'reachableSymbols': finds all potential callers. +-- Returns empty set if the target symbol doesn't exist. +reachingSymbols :: Text -> CallGraph -> Set Text +reachingSymbols target cg = case cgVertexFromK cg target of + Nothing -> Set.empty + Just v -> Set.fromList + [sym | v' <- reachable (transposeG (cgGraph cg)) v + , let (_, sym, _) = cgNodeFromV cg v'] + -- | Get all symbols in the call graph. allSymbols :: CallGraph -> Set Text allSymbols = cgSymbols