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:
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