auditor

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

NCT.hs (4528B)


      1 {-# OPTIONS_HADDOCK prune #-}
      2 {-# LANGUAGE OverloadedStrings #-}
      3 
      4 -- |
      5 -- Module: Audit.AArch64.NCT
      6 -- Copyright: (c) 2025 Jared Tobin
      7 -- License: MIT
      8 -- Maintainer: jared@ppad.tech
      9 --
     10 -- Static non-constant-time instruction scanner for AArch64
     11 -- assembly. Flags instructions that typically introduce
     12 -- timing variability.
     13 
     14 module Audit.AArch64.NCT (
     15     -- * Scanner
     16     scanNct
     17     -- * Runtime-aware filtering
     18   , buildLineMap
     19   , filterRuntimePatterns
     20   ) where
     21 
     22 import Audit.AArch64.CFG (isFunctionLabel)
     23 import Audit.AArch64.Runtime (RuntimeConfig(..))
     24 import Audit.AArch64.Types
     25 import qualified Data.IntMap.Strict as IntMap
     26 import Data.Map.Strict (Map)
     27 import qualified Data.Map.Strict as Map
     28 import Data.Text (Text)
     29 
     30 -- | Scan parsed lines for non-constant-time instructions.
     31 -- Returns findings grouped by function symbol.
     32 scanNct :: RuntimeConfig -> [Line]
     33         -> Map Text [NctFinding]
     34 scanNct rt = finalize . foldl' step (unknownSym, Map.empty)
     35   where
     36     unknownSym = "<unknown>"
     37 
     38     step (curSym, acc) ln =
     39       let sym' = case lineLabel ln of
     40             Just lbl | isFunctionLabel rt lbl -> lbl
     41             _ -> curSym
     42           findings = classifyLine ln
     43           acc' = case findings of
     44             [] -> acc
     45             _  -> Map.insertWith (++) sym' findings acc
     46       in  (sym', acc')
     47 
     48     finalize (_, acc) = fmap reverse acc
     49 
     50 -- | Classify a line, returning any NCT findings.
     51 classifyLine :: Line -> [NctFinding]
     52 classifyLine ln = case lineInstr ln of
     53   Nothing -> []
     54   Just instr -> case classifyInstr instr of
     55     Nothing -> []
     56     Just reason ->
     57       [NctFinding (lineNum ln) instr reason]
     58 
     59 -- | Classify an instruction for NCT concerns.
     60 classifyInstr :: Instr -> Maybe NctReason
     61 classifyInstr instr = case instr of
     62   -- Conditional branches
     63   BCond _ _   -> Just CondBranch
     64   Cbz _ _     -> Just CondBranch
     65   Cbnz _ _    -> Just CondBranch
     66   Tbz _ _ _   -> Just CondBranch
     67   Tbnz _ _ _  -> Just CondBranch
     68 
     69   -- Indirect branches
     70   Br _        -> Just IndirectBranch
     71   Blr _       -> Just IndirectBranch
     72 
     73   -- Division (not DIT per ARM docs)
     74   Udiv _ _ _  -> Just Div
     75   Sdiv _ _ _  -> Just Div
     76 
     77   -- Load/store with register index
     78   Ldr _ addr   -> checkRegIndexAddr addr
     79   Ldrb _ addr  -> checkRegIndexAddr addr
     80   Ldrh _ addr  -> checkRegIndexAddr addr
     81   Ldrsb _ addr -> checkRegIndexAddr addr
     82   Ldrsh _ addr -> checkRegIndexAddr addr
     83   Ldrsw _ addr -> checkRegIndexAddr addr
     84   Ldur _ addr  -> checkRegIndexAddr addr
     85   Str _ addr   -> checkRegIndexAddr addr
     86   Strb _ addr  -> checkRegIndexAddr addr
     87   Strh _ addr  -> checkRegIndexAddr addr
     88   Stur _ addr  -> checkRegIndexAddr addr
     89   Ldp _ _ addr -> checkRegIndexAddr addr
     90   Stp _ _ addr -> checkRegIndexAddr addr
     91 
     92   -- Acquire/release loads
     93   Ldar _ addr   -> checkRegIndexAddr addr
     94   Ldarb _ addr  -> checkRegIndexAddr addr
     95   Ldarh _ addr  -> checkRegIndexAddr addr
     96   Stlr _ addr   -> checkRegIndexAddr addr
     97   Stlrb _ addr  -> checkRegIndexAddr addr
     98   Stlrh _ addr  -> checkRegIndexAddr addr
     99 
    100   -- Exclusive loads/stores
    101   Ldxr _ addr    -> checkRegIndexAddr addr
    102   Ldxrb _ addr   -> checkRegIndexAddr addr
    103   Ldxrh _ addr   -> checkRegIndexAddr addr
    104   Stxr _ _ addr  -> checkRegIndexAddr addr
    105   Stxrb _ _ addr -> checkRegIndexAddr addr
    106   Stxrh _ _ addr -> checkRegIndexAddr addr
    107 
    108   -- Acquire-exclusive and release-exclusive
    109   Ldaxr _ addr    -> checkRegIndexAddr addr
    110   Ldaxrb _ addr   -> checkRegIndexAddr addr
    111   Ldaxrh _ addr   -> checkRegIndexAddr addr
    112   Stlxr _ _ addr  -> checkRegIndexAddr addr
    113   Stlxrb _ _ addr -> checkRegIndexAddr addr
    114   Stlxrh _ _ addr -> checkRegIndexAddr addr
    115 
    116   _ -> Nothing
    117 
    118 -- | Check if address mode uses register indexing.
    119 checkRegIndexAddr :: AddrMode -> Maybe NctReason
    120 checkRegIndexAddr addr = case addr of
    121   BaseReg _ _        -> Just RegIndexAddr
    122   BaseRegShift _ _ _ -> Just RegIndexAddr
    123   BaseRegExtend _ _ _ -> Just RegIndexAddr
    124   _ -> Nothing
    125 
    126 -- | Build a line map from parsed lines for O(1) lookup.
    127 buildLineMap :: [Line] -> LineMap
    128 buildLineMap lns =
    129   IntMap.fromList [(lineNum l, l) | l <- lns]
    130 
    131 -- | Filter out runtime-specific patterns from NCT findings.
    132 filterRuntimePatterns
    133   :: RuntimeConfig -> [Line]
    134   -> Map Text [NctFinding] -> Map Text [NctFinding]
    135 filterRuntimePatterns rt lns findings =
    136   Map.mapMaybe filterFindings findings
    137   where
    138     lineMap = buildLineMap lns
    139     filterFn = rtFilterNct rt
    140 
    141     filterFindings :: [NctFinding] -> Maybe [NctFinding]
    142     filterFindings fs =
    143       let fs' = filter (not . filterFn lineMap) fs
    144       in  if null fs' then Nothing else Just fs'