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'