auditor

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

commit 50e1c801e8d53f2da47b11b8b858a6978f800104
parent 193e20530319c6eba1f81ea7ee1f5f023b885881
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 13 Feb 2026 10:52:04 +0400

feat: filter GHC closure table lookups from NCT findings (IMPL23)

Register-indexed loads from *_closure_tbl symbols (e.g., Bool_closure_tbl)
are GHC runtime boxing operations, not secret-dependent table lookups.

Pattern detected:
  adrp x9, _ghczmprim_GHCziTypes_Bool_closure_tbl@GOTPAGE
  ldr  x9, [x9, ...]
  ldr  x22, [x9, w8, uxtw #3]  ; <-- was flagged as reg-index

Now scans backwards within BB to check if base register was loaded from
a closure table symbol.

secp256k1.s: 252 -> 246 findings (6 Bool boxing operations filtered)

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

Diffstat:
Mlib/Audit/AArch64/NCT.hs | 33+++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+), 0 deletions(-)

diff --git a/lib/Audit/AArch64/NCT.hs b/lib/Audit/AArch64/NCT.hs @@ -179,6 +179,7 @@ isGhcRuntimeFinding lineMap f = case nctReason f of CondBranch -> isHeapCheck || isNurseryCheck || isTagCheck || isCafCheck || isArityCheck IndirectBranch -> isClosureEntry || isDictCall || isRtsCall + RegIndexAddr -> isClosureTableLookup _ -> False where -- Check if conditional branch is a heap check (prev: cmp <r>, x28) @@ -342,6 +343,38 @@ isGhcRuntimeFinding lineMap f = case nctReason f of Ldur r (BaseImm X19 _) -> r == reg _ -> False + -- Check if register-indexed access is a GHC closure table lookup + -- Pattern: adrp <base>, *_closure_tbl*; ldr <base>, [...]; ldr [base, idx] + -- Used for boxing Bool, Maybe constructors, etc. + isClosureTableLookup :: Bool + isClosureTableLookup = case getBaseReg (nctInstr f) of + Nothing -> False + Just reg -> any (isClosureTableLoad reg) bbInstrs + + -- Extract base register from a reg-indexed load/store + getBaseReg :: Instr -> Maybe Reg + getBaseReg instr = case instr of + Ldr _ addr -> baseOfRegIndex addr + Ldrb _ addr -> baseOfRegIndex addr + Ldrh _ addr -> baseOfRegIndex addr + Str _ addr -> baseOfRegIndex addr + Strb _ addr -> baseOfRegIndex addr + Strh _ addr -> baseOfRegIndex addr + _ -> Nothing + + baseOfRegIndex :: AddrMode -> Maybe Reg + baseOfRegIndex addr = case addr of + BaseReg base _ -> Just base + BaseRegShift base _ _ -> Just base + BaseRegExtend base _ _ -> Just base + _ -> Nothing + + -- Check if instruction loads closure table address into register + isClosureTableLoad :: Reg -> Instr -> Bool + isClosureTableLoad reg instr = case instr of + Adrp r label -> samePhysicalReg r reg && "_closure_tbl" `isInfixOf` label + _ -> False + -- Get instruction from preceding line prevInstr :: Maybe Instr prevInstr = do