auditor

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

Main.hs (64974B)


      1 {-# LANGUAGE OverloadedStrings #-}
      2 
      3 module Main where
      4 
      5 import Audit.AArch64
      6 import Audit.AArch64.CallGraph
      7 import Audit.AArch64.Parser
      8 import Audit.AArch64.Runtime.GHC (ghcRuntime)
      9 import Audit.AArch64.Taint
     10 import Audit.AArch64.Types
     11 import Data.Aeson (eitherDecodeStrict')
     12 import qualified Data.ByteString.Char8 as B8
     13 import qualified Data.Map.Strict as Map
     14 import qualified Data.Set as Set
     15 import qualified Data.Text as T
     16 import Test.Tasty
     17 import Test.Tasty.HUnit
     18 
     19 -- | Runtime config used throughout tests.
     20 rt :: RuntimeConfig
     21 rt = ghcRuntime
     22 
     23 -- | Check if a violation is secret-derived
     24 -- (not unknown or structural).
     25 isSecretViolation :: ViolationReason -> Bool
     26 isSecretViolation r = case r of
     27   SecretBase _     -> True
     28   SecretIndex _    -> True
     29   UnknownBase _    -> False
     30   UnknownIndex _   -> False
     31   NonConstOffset   -> False
     32 
     33 main :: IO ()
     34 main = defaultMain $ testGroup "ppad-auditor" [
     35     parserTests
     36   , taintTests
     37   , auditTests
     38   , interprocTests
     39   , provenanceTests
     40   , taintConfigTests
     41   , nctTests
     42   , callGraphTests
     43   , tailCallTests
     44   ]
     45 
     46 -- Parser tests
     47 
     48 parserTests :: TestTree
     49 parserTests = testGroup "Parser" [
     50     testCase "parse label" $ do
     51       let src = "foo:\n"
     52       case parseAsm src of
     53         Left _ -> assertFailure "parse failed"
     54         Right lns -> do
     55           assertEqual "line count" 1 (length lns)
     56           assertEqual "label" (Just "foo")
     57             (lineLabel (head lns))
     58 
     59   , testCase "parse ldr base+imm" $ do
     60       let src = "ldr x0, [x20, #8]\n"
     61       case parseAsm src of
     62         Left _ -> assertFailure "parse failed"
     63         Right lns -> case lineInstr (head lns) of
     64           Just (Ldr X0 (BaseImm X20 8)) -> pure ()
     65           other -> assertFailure $
     66             "unexpected: " ++ show other
     67 
     68   , testCase "parse str base+reg" $ do
     69       let src = "str x1, [x21, x2]\n"
     70       case parseAsm src of
     71         Left _ -> assertFailure "parse failed"
     72         Right lns -> case lineInstr (head lns) of
     73           Just (Str X1 (BaseReg X21 X2)) -> pure ()
     74           other -> assertFailure $
     75             "unexpected: " ++ show other
     76 
     77   , testCase "parse add" $ do
     78       let src = "add x0, x1, x2\n"
     79       case parseAsm src of
     80         Left _ -> assertFailure "parse failed"
     81         Right lns -> case lineInstr (head lns) of
     82           Just (Add X0 X1 (OpReg X2)) -> pure ()
     83           other -> assertFailure $
     84             "unexpected: " ++ show other
     85 
     86   , testCase "parse adrp" $ do
     87       let src = "adrp x5, _symbol@PAGE\n"
     88       case parseAsm src of
     89         Left _ -> assertFailure "parse failed"
     90         Right lns -> case lineInstr (head lns) of
     91           Just (Adrp X5 _) -> pure ()
     92           other -> assertFailure $
     93             "unexpected: " ++ show other
     94 
     95   , testCase "skip directives" $ do
     96       let src = ".section __TEXT\n\
     97                 \.globl _foo\n_foo:\n  ret\n"
     98       case parseAsm src of
     99         Left e -> assertFailure $
    100           "parse failed: " ++ show e
    101         Right lns -> do
    102           let instrs = filter
    103                 ((/= Nothing) . lineInstr) lns
    104           assertEqual "instruction count"
    105             1 (length instrs)
    106 
    107   , testCase "parse ldr base+symbol" $ do
    108       let src = "ldr x8, [x8, _foo@GOTPAGEOFF]\n"
    109       case parseAsm src of
    110         Left e -> assertFailure $
    111           "parse failed: " ++ show e
    112         Right lns ->
    113           case lineInstr (safeHead lns) of
    114             Just (Ldr X8
    115               (BaseSymbol X8 "_foo@GOTPAGEOFF"))
    116               -> pure ()
    117             other -> assertFailure $
    118               "unexpected: " ++ show other
    119 
    120   , testCase "parse shifted register operand" $ do
    121       let src = "add x0, x1, x2, lsl #3\n"
    122       case parseAsm src of
    123         Left e -> assertFailure $
    124           "parse failed: " ++ show e
    125         Right lns ->
    126           case lineInstr (safeHead lns) of
    127             Just (Add X0 X1
    128               (OpShiftedReg X2 (LSL 3))) -> pure ()
    129             other -> assertFailure $
    130               "unexpected: " ++ show other
    131 
    132   , testCase "parse shifted immediate operand" $ do
    133       -- regression: `cmp x8, #16, lsl #12` is the uimm12
    134       -- shifted-immediate form (16 << 12 = 65536). The
    135       -- parser must fold the shift into the literal.
    136       let src = "cmp x8, #16, lsl #12\n"
    137       case parseAsm src of
    138         Left e -> assertFailure $
    139           "parse failed: " ++ show e
    140         Right lns ->
    141           case lineInstr (safeHead lns) of
    142             Just (Cmp X8 (OpImm 65536)) -> pure ()
    143             other -> assertFailure $
    144               "unexpected: " ++ show other
    145 
    146   , testCase "parse plain register operand" $ do
    147       let src = "add x0, x1, x2\n"
    148       case parseAsm src of
    149         Left e -> assertFailure $
    150           "parse failed: " ++ show e
    151         Right lns ->
    152           case lineInstr (safeHead lns) of
    153             Just (Add X0 X1 (OpReg X2)) -> pure ()
    154             other -> assertFailure $
    155               "unexpected: " ++ show other
    156 
    157   , testCase "parse literal address" $ do
    158       let src = "ldr x0, =foo\n"
    159       case parseAsm src of
    160         Left e -> assertFailure $
    161           "parse failed: " ++ show e
    162         Right lns ->
    163           case lineInstr (safeHead lns) of
    164             Just (Ldr X0 (Literal "foo")) -> pure ()
    165             other -> assertFailure $
    166               "unexpected: " ++ show other
    167 
    168   , testCase "parse all register types" $ do
    169       let src = T.unlines
    170             [ "mov x0, x1"
    171             , "mov w0, w1"
    172             , "mov d0, d1"
    173             , "mov s0, s1"
    174             , "mov q0, q1"
    175             , "mov x0, sp"
    176             , "mov x0, xzr"
    177             , "mov w0, wzr"
    178             , "mov x0, fp"
    179             , "mov x0, lr"
    180             ]
    181       case parseAsm src of
    182         Left e -> assertFailure $
    183           "parse failed: " ++ show e
    184         Right lns ->
    185           assertEqual "parsed all lines"
    186             10 (length lns)
    187 
    188   , testCase "parse label with instruction" $ do
    189       let src = "foo: mov x0, x1\n"
    190       case parseAsm src of
    191         Left e -> assertFailure $
    192           "parse failed: " ++ show e
    193         Right lns -> case safeHead lns of
    194           Line _ (Just "foo")
    195             (Just (Mov X0 (OpReg X1))) -> pure ()
    196           other -> assertFailure $
    197             "unexpected: " ++ show other
    198 
    199   , testCase "parse instruction without label" $ do
    200       let src = "  mov x0, x1\n"
    201       case parseAsm src of
    202         Left e -> assertFailure $
    203           "parse failed: " ++ show e
    204         Right lns -> case safeHead lns of
    205           Line _ Nothing
    206             (Just (Mov X0 (OpReg X1))) -> pure ()
    207           other -> assertFailure $
    208             "unexpected: " ++ show other
    209 
    210   -- Acquire/release and exclusive memory ops
    211   , testCase "parse ldar" $ do
    212       let src = "ldar x0, [x1]\n"
    213       case parseAsm src of
    214         Left e -> assertFailure $
    215           "parse failed: " ++ show e
    216         Right lns ->
    217           case lineInstr (safeHead lns) of
    218             Just (Ldar X0 (BaseImm X1 0)) ->
    219               pure ()
    220             other -> assertFailure $
    221               "unexpected: " ++ show other
    222 
    223   , testCase "parse stlr" $ do
    224       let src = "stlr x0, [x1]\n"
    225       case parseAsm src of
    226         Left e -> assertFailure $
    227           "parse failed: " ++ show e
    228         Right lns ->
    229           case lineInstr (safeHead lns) of
    230             Just (Stlr X0 (BaseImm X1 0)) ->
    231               pure ()
    232             other -> assertFailure $
    233               "unexpected: " ++ show other
    234 
    235   , testCase "parse stxr" $ do
    236       let src = "stxr w0, x1, [x2]\n"
    237       case parseAsm src of
    238         Left e -> assertFailure $
    239           "parse failed: " ++ show e
    240         Right lns ->
    241           case lineInstr (safeHead lns) of
    242             Just (Stxr W0 X1 (BaseImm X2 0)) ->
    243               pure ()
    244             other -> assertFailure $
    245               "unexpected: " ++ show other
    246 
    247   , testCase "parse ldaxr" $ do
    248       let src = "ldaxr x0, [x1]\n"
    249       case parseAsm src of
    250         Left e -> assertFailure $
    251           "parse failed: " ++ show e
    252         Right lns ->
    253           case lineInstr (safeHead lns) of
    254             Just (Ldaxr X0 (BaseImm X1 0)) ->
    255               pure ()
    256             other -> assertFailure $
    257               "unexpected: " ++ show other
    258 
    259   , testCase "parse stlxr" $ do
    260       let src = "stlxr w0, x1, [x2]\n"
    261       case parseAsm src of
    262         Left e -> assertFailure $
    263           "parse failed: " ++ show e
    264         Right lns ->
    265           case lineInstr (safeHead lns) of
    266             Just (Stlxr W0 X1 (BaseImm X2 0)) ->
    267               pure ()
    268             other -> assertFailure $
    269               "unexpected: " ++ show other
    270   ]
    271 
    272 safeHead :: [a] -> a
    273 safeHead (x:_) = x
    274 safeHead []    = error "safeHead: empty list"
    275 
    276 -- Taint tests
    277 
    278 taintTests :: TestTree
    279 taintTests = testGroup "Taint" [
    280     testCase "public roots" $ do
    281       let st = initTaintState rt
    282       assertEqual "X20 is public"
    283         Public (getTaint X20 st)
    284       assertEqual "X21 is public"
    285         Public (getTaint X21 st)
    286       assertEqual "SP is public"
    287         Public (getTaint SP st)
    288 
    289   , testCase "unknown register" $ do
    290       let st = initTaintState rt
    291       assertEqual "X0 is unknown"
    292         Unknown (getTaint X0 st)
    293 
    294   , testCase "mov propagates taint" $ do
    295       let st = initTaintState rt
    296           l = Line 1 Nothing
    297                 (Just (Mov X0 (OpReg X20)))
    298           st' = analyzeLine rt l st
    299       assertEqual "X0 becomes public"
    300         Public (getTaint X0 st')
    301 
    302   , testCase "add joins taints" $ do
    303       let st = initTaintState rt
    304           l = Line 1 Nothing
    305                 (Just (Add X0 X20 (OpReg X1)))
    306           st' = analyzeLine rt l st
    307       assertEqual "result is unknown"
    308         Unknown (getTaint X0 st')
    309 
    310   , testCase "load makes unknown" $ do
    311       let st = initTaintStateWith rt False
    312           l = Line 1 Nothing
    313                 (Just (Ldr X0 (BaseImm X20 0)))
    314           st' = analyzeLine rt l st
    315       assertEqual "loaded value is unknown"
    316         Unknown (getTaint X0 st')
    317   ]
    318 
    319 -- Audit tests
    320 
    321 auditTests :: TestTree
    322 auditTests = testGroup "Audit" [
    323     testCase "good: public base" $ do
    324       let src = T.unlines
    325             [ "foo:"
    326             , "  ldr x0, [x20, #8]"
    327             , "  str x0, [x21]"
    328             , "  ret"
    329             ]
    330       case audit rt "test" src of
    331         Left e -> assertFailure $
    332           "parse failed: " ++ show e
    333         Right ar -> assertEqual "no violations"
    334           0 (length (arViolations ar))
    335 
    336   , testCase "bad: unknown base" $ do
    337       let src = T.unlines
    338             [ "foo:"
    339             , "  ldr x0, [x0]"
    340             , "  ret"
    341             ]
    342       case audit rt "test" src of
    343         Left e -> assertFailure $
    344           "parse failed: " ++ show e
    345         Right ar -> do
    346           assertEqual "one violation"
    347             1 (length (arViolations ar))
    348           case arViolations ar of
    349             [v] -> case vReason v of
    350               UnknownBase X0 -> pure ()
    351               other -> assertFailure $
    352                 "wrong reason: " ++ show other
    353             _ -> assertFailure
    354               "expected one violation"
    355 
    356   , testCase "bad: secret-derived index" $ do
    357       let src = T.unlines
    358             [ "foo:"
    359             , "  ldr x0, [x21]"
    360             , "  ldr x1, [x20, x0]"
    361             , "  ret"
    362             ]
    363       case audit rt "test" src of
    364         Left e -> assertFailure $
    365           "parse failed: " ++ show e
    366         Right ar -> do
    367           assertEqual "one violation"
    368             1 (length (arViolations ar))
    369           case arViolations ar of
    370             [v] -> case vReason v of
    371               UnknownIndex X0 -> pure ()
    372               other -> assertFailure $
    373                 "wrong reason: " ++ show other
    374             _ -> assertFailure
    375               "expected one violation"
    376 
    377   , testCase "good: adrp+add pattern" $ do
    378       let src = T.unlines
    379             [ "foo:"
    380             , "  adrp x8, _const@PAGE"
    381             , "  add x8, x8, _const@PAGEOFF"
    382             , "  ldr x0, [x8]"
    383             , "  ret"
    384             ]
    385       case audit rt "test" src of
    386         Left e -> assertFailure $
    387           "parse failed: " ++ show e
    388         Right ar -> assertEqual "no violations"
    389           0 (length (arViolations ar))
    390 
    391   , testCase "good: cross-block public taint" $ do
    392       let src = T.unlines
    393             [ "blockA:"
    394             , "  adrp x8, _const@PAGE"
    395             , "  cbz x0, blockB"
    396             , "blockB:"
    397             , "  ldr x1, [x8]"
    398             , "  ret"
    399             ]
    400       case audit rt "test" src of
    401         Left e -> assertFailure $
    402           "parse failed: " ++ show e
    403         Right ar -> assertEqual "no violations"
    404           0 (length (arViolations ar))
    405 
    406   , testCase "bad: cross-block unknown taint" $ do
    407       let src = T.unlines
    408             [ "blockA:"
    409             , "  ldr x8, [x21]"
    410             , "  cbz x0, blockB"
    411             , "blockB:"
    412             , "  ldr x1, [x8]"
    413             , "  ret"
    414             ]
    415       case audit rt "test" src of
    416         Left e -> assertFailure $
    417           "parse failed: " ++ show e
    418         Right ar -> do
    419           assertEqual "one violation"
    420             1 (length (arViolations ar))
    421           case arViolations ar of
    422             [v] -> case vReason v of
    423               UnknownBase X8 -> pure ()
    424               other -> assertFailure $
    425                 "wrong reason: " ++ show other
    426             _ -> assertFailure
    427               "expected one violation"
    428 
    429   , testCase "call: no taint propagation to callee" $ do
    430       let src = T.unlines
    431             [ "caller:"
    432             , "  adrp x8, _const@PAGE"
    433             , "  bl callee"
    434             , "  ret"
    435             , "callee:"
    436             , "  ldr x0, [x8]"
    437             , "  ret"
    438             ]
    439       case audit rt "test" src of
    440         Left e -> assertFailure $
    441           "parse failed: " ++ show e
    442         Right ar -> do
    443           assertEqual "one violation"
    444             1 (length (arViolations ar))
    445           case arViolations ar of
    446             [v] -> do
    447               assertEqual "violation in callee"
    448                 "callee" (vSymbol v)
    449               case vReason v of
    450                 UnknownBase X8 -> pure ()
    451                 other -> assertFailure $
    452                   "wrong reason: " ++ show other
    453             _ -> assertFailure
    454               "expected one violation"
    455 
    456   , testCase "call: caller-saved invalidation" $ do
    457       let src = T.unlines
    458             [ "foo:"
    459             , "  adrp x0, _const@PAGE"
    460             , "  bl bar"
    461             , "  ldr x1, [x0]"
    462             , "  ret"
    463             ]
    464       case audit rt "test" src of
    465         Left e -> assertFailure $
    466           "parse failed: " ++ show e
    467         Right ar -> do
    468           assertEqual "one violation"
    469             1 (length (arViolations ar))
    470           case arViolations ar of
    471             [v] -> case vReason v of
    472               UnknownBase X0 -> pure ()
    473               other -> assertFailure $
    474                 "wrong reason: " ++ show other
    475             _ -> assertFailure
    476               "expected one violation"
    477 
    478   , testCase "call: callee-saved preserved" $ do
    479       let src = T.unlines
    480             [ "foo:"
    481             , "  bl bar"
    482             , "  ldr x0, [x19]"
    483             , "  ret"
    484             ]
    485       case audit rt "test" src of
    486         Left e -> assertFailure $
    487           "parse failed: " ++ show e
    488         Right ar -> assertEqual "no violations"
    489           0 (length (arViolations ar))
    490 
    491   , testCase "good: stack spill/reload preserves public" $ do
    492       let src = T.unlines
    493             [ "foo:"
    494             , "  stp x20, x21, [sp]"
    495             , "  ldr x8, [sp]"
    496             , "  ldr x9, [sp, #8]"
    497             , "  ldr x0, [x8]"
    498             , "  ldr x1, [x9]"
    499             , "  ret"
    500             ]
    501       case audit rt "test" src of
    502         Left e -> assertFailure $
    503           "parse failed: " ++ show e
    504         Right ar -> assertEqual "no violations"
    505           0 (length (arViolations ar))
    506 
    507   , testCase "bad: stack spill/reload preserves unknown" $ do
    508       let src = T.unlines
    509             [ "foo:"
    510             , "  ldr x8, [x21]"
    511             , "  str x8, [sp, #16]"
    512             , "  ldr x9, [sp, #16]"
    513             , "  ldr x0, [x9]"
    514             , "  ret"
    515             ]
    516       case audit rt "test" src of
    517         Left e -> assertFailure $
    518           "parse failed: " ++ show e
    519         Right ar -> do
    520           assertEqual "one violation"
    521             1 (length (arViolations ar))
    522           case arViolations ar of
    523             [v] -> case vReason v of
    524               UnknownBase X9 -> pure ()
    525               other -> assertFailure $
    526                 "wrong reason: " ++ show other
    527             _ -> assertFailure
    528               "expected one violation"
    529 
    530   , testCase "good: pointer spill/reload preserves kind" $ do
    531       let src = T.unlines
    532             [ "foo:"
    533             , "  adrp x8, _const@PAGE"
    534             , "  str x8, [sp, #16]"
    535             , "  ldr x9, [sp, #16]"
    536             , "  ldr x0, [x9]"
    537             , "  ret"
    538             ]
    539       case audit rt "test" src of
    540         Left e -> assertFailure $
    541           "parse failed: " ++ show e
    542         Right ar -> assertEqual "no violations"
    543           0 (length (arViolations ar))
    544 
    545   , testCase "bad: scalar spill/reload doesn't upgrade kind" $ do
    546       let src = T.unlines
    547             [ "foo:"
    548             , "  ldr x8, [x21]"
    549             , "  add x8, x20, x8"
    550             , "  str x8, [sp, #16]"
    551             , "  ldr x9, [sp, #16]"
    552             , "  ldr x0, [x9]"
    553             , "  ret"
    554             ]
    555       case audit rt "test" src of
    556         Left e -> assertFailure $
    557           "parse failed: " ++ show e
    558         Right ar -> do
    559           assertEqual "one violation"
    560             1 (length (arViolations ar))
    561           case arViolations ar of
    562             [v] -> case vReason v of
    563               UnknownBase X9 -> pure ()
    564               other -> assertFailure $
    565                 "wrong reason: " ++ show other
    566             _ -> assertFailure
    567               "expected one violation"
    568 
    569   -- STG stack (x20-relative) spill/reload tests
    570   , testCase "good: STG stack spill/reload preserves public" $ do
    571       let src = T.unlines
    572             [ "foo:"
    573             , "  adrp x8, _const@PAGE"
    574             , "  str x8, [x20, #8]"
    575             , "  ldr x9, [x20, #8]"
    576             , "  ldr x0, [x9]"
    577             , "  ret"
    578             ]
    579       case audit rt "test" src of
    580         Left e -> assertFailure $
    581           "parse failed: " ++ show e
    582         Right ar -> assertEqual "no violations"
    583           0 (length (arViolations ar))
    584 
    585   , testCase "bad: STG stack spill/reload preserves unknown" $ do
    586       let src = T.unlines
    587             [ "foo:"
    588             , "  ldr x8, [x21]"
    589             , "  str x8, [x20, #16]"
    590             , "  ldr x9, [x20, #16]"
    591             , "  ldr x0, [x9]"
    592             , "  ret"
    593             ]
    594       case audit rt "test" src of
    595         Left e -> assertFailure $
    596           "parse failed: " ++ show e
    597         Right ar -> do
    598           assertEqual "one violation"
    599             1 (length (arViolations ar))
    600           case arViolations ar of
    601             [v] -> case vReason v of
    602               UnknownBase X9 -> pure ()
    603               other -> assertFailure $
    604                 "wrong reason: " ++ show other
    605             _ -> assertFailure
    606               "expected one violation"
    607 
    608   , testCase "good: STG stack pointer spill preserves kind" $ do
    609       let src = T.unlines
    610             [ "foo:"
    611             , "  adrp x8, _const@PAGE"
    612             , "  str x8, [x20, #24]"
    613             , "  ldr x9, [x20, #24]"
    614             , "  ldr x0, [x9]"
    615             , "  ret"
    616             ]
    617       case audit rt "test" src of
    618         Left e -> assertFailure $
    619           "parse failed: " ++ show e
    620         Right ar -> assertEqual "no violations"
    621           0 (length (arViolations ar))
    622 
    623   , testCase "good: STG stack pre-indexed load preserves taint" $ do
    624       let src = T.unlines
    625             [ "foo:"
    626             , "  adrp x8, _const@PAGE"
    627             , "  str x8, [x20, #16]"
    628             , "  ldr x9, [x20, #16]!"
    629             , "  ldr x0, [x9]"
    630             , "  ret"
    631             ]
    632       case audit rt "test" src of
    633         Left e -> assertFailure $
    634           "parse failed: " ++ show e
    635         Right ar -> assertEqual "no violations"
    636           0 (length (arViolations ar))
    637 
    638   , testCase "good: GOT/PLT pattern" $ do
    639       let src = T.unlines
    640             [ "foo:"
    641             , "  adrp x8, _sym@GOTPAGE"
    642             , "  ldr x8, [x8, _sym@GOTPAGEOFF]"
    643             , "  ldr x0, [x8]"
    644             , "  ret"
    645             ]
    646       case audit rt "test" src of
    647         Left e -> assertFailure $
    648           "parse failed: " ++ show e
    649         Right ar -> assertEqual "no violations"
    650           0 (length (arViolations ar))
    651 
    652   -- Heap taint propagation tests
    653   , testCase "bad: heap store propagates secret to later load" $ do
    654       let src = T.unlines
    655             [ "_foo:"
    656             , "  str x0, [x21]"
    657             , "  ldr x9, [x21, #8]"
    658             , "  ldr x1, [x20, x9]"
    659             , "  ret"
    660             ]
    661           cfg = TaintConfig (Map.singleton "_foo"
    662             (ArgPolicy (Set.singleton X0)
    663               Set.empty Set.empty
    664               Set.empty Set.empty)) True
    665       case auditWithConfig rt cfg "test" src of
    666         Left e -> assertFailure $
    667           "parse failed: " ++ show e
    668         Right ar -> do
    669           assertEqual "one violation"
    670             1 (length (arViolations ar))
    671           case arViolations ar of
    672             [v] -> case vReason v of
    673               SecretIndex X9 -> pure ()
    674               other -> assertFailure $
    675                 "wrong reason: " ++ show other
    676             _ -> assertFailure
    677               "expected one violation"
    678 
    679   , testCase "bad: heap taints join on multiple stores" $ do
    680       let src = T.unlines
    681             [ "_foo:"
    682             , "  adrp x8, _const@PAGE"
    683             , "  str x8, [x21]"
    684             , "  str x0, [x21, #8]"
    685             , "  ldr x9, [x21, #16]"
    686             , "  ldr x1, [x20, x9]"
    687             , "  ret"
    688             ]
    689           cfg = TaintConfig (Map.singleton "_foo"
    690             (ArgPolicy (Set.singleton X0)
    691               Set.empty Set.empty
    692               Set.empty Set.empty)) True
    693       case auditWithConfig rt cfg "test" src of
    694         Left e -> assertFailure $
    695           "parse failed: " ++ show e
    696         Right ar -> do
    697           assertEqual "one violation"
    698             1 (length (arViolations ar))
    699           case arViolations ar of
    700             [v] -> case vReason v of
    701               SecretIndex X9 -> pure ()
    702               other -> assertFailure $
    703                 "wrong reason: " ++ show other
    704             _ -> assertFailure
    705               "expected one violation"
    706 
    707   -- Refined heap slot tests
    708   , testCase "good: refined heap slot preserves public at different offset" $ do
    709       let src = T.unlines
    710             [ "_foo:"
    711             , "  adrp x8, _const@PAGE"
    712             , "  str x0, [x21]"
    713             , "  str x8, [x21, #8]"
    714             , "  ldr x9, [x21, #8]"
    715             , "  ldr x10, [x9]"
    716             , "  ret"
    717             ]
    718           cfg = TaintConfig (Map.singleton "_foo"
    719             (ArgPolicy (Set.singleton X0)
    720               Set.empty Set.empty
    721               Set.empty Set.empty)) True
    722       case auditWithConfig rt cfg "test" src of
    723         Left e -> assertFailure $
    724           "parse failed: " ++ show e
    725         Right ar -> assertEqual "no violations"
    726           0 (length (arViolations ar))
    727 
    728   , testCase "bad: refined heap slot preserves secret at its offset" $ do
    729       let src = T.unlines
    730             [ "_foo:"
    731             , "  adrp x8, _const@PAGE"
    732             , "  str x8, [x21]"
    733             , "  str x0, [x21, #8]"
    734             , "  ldr x9, [x21, #8]"
    735             , "  ldr x10, [x20, x9]"
    736             , "  ret"
    737             ]
    738           cfg = TaintConfig (Map.singleton "_foo"
    739             (ArgPolicy (Set.singleton X0)
    740               Set.empty Set.empty
    741               Set.empty Set.empty)) True
    742       case auditWithConfig rt cfg "test" src of
    743         Left e -> assertFailure $
    744           "parse failed: " ++ show e
    745         Right ar -> do
    746           assertEqual "one violation"
    747             1 (length (arViolations ar))
    748           case arViolations ar of
    749             [v] -> case vReason v of
    750               SecretIndex X9 -> pure ()
    751               other -> assertFailure $
    752                 "wrong reason: " ++ show other
    753             _ -> assertFailure
    754               "expected one violation"
    755 
    756   , testCase "bad: refined heap slot falls back for unknown offset" $ do
    757       let src = T.unlines
    758             [ "_foo:"
    759             , "  str x0, [x21]"
    760             , "  ldr x9, [x21, #16]"
    761             , "  ldr x10, [x20, x9]"
    762             , "  ret"
    763             ]
    764           cfg = TaintConfig (Map.singleton "_foo"
    765             (ArgPolicy (Set.singleton X0)
    766               Set.empty Set.empty
    767               Set.empty Set.empty)) True
    768       case auditWithConfig rt cfg "test" src of
    769         Left e -> assertFailure $
    770           "parse failed: " ++ show e
    771         Right ar -> do
    772           assertEqual "one violation"
    773             1 (length (arViolations ar))
    774           case arViolations ar of
    775             [v] -> case vReason v of
    776               SecretIndex X9 -> pure ()
    777               other -> assertFailure $
    778                 "wrong reason: " ++ show other
    779             _ -> assertFailure
    780               "expected one violation"
    781 
    782   -- Acquire/release and exclusive memory ops
    783   , testCase "bad: ldar from unknown base" $ do
    784       let src = T.unlines
    785             [ "_foo:"
    786             , "  ldar x8, [x0]"
    787             , "  ret"
    788             ]
    789       case audit rt "test" src of
    790         Left e -> assertFailure $
    791           "parse failed: " ++ show e
    792         Right ar -> do
    793           assertEqual "one violation"
    794             1 (length (arViolations ar))
    795           case arViolations ar of
    796             [v] -> case vReason v of
    797               UnknownBase X0 -> pure ()
    798               other -> assertFailure $
    799                 "wrong reason: " ++ show other
    800             _ -> assertFailure
    801               "expected one violation"
    802 
    803   , testCase "good: stlr to stack preserves taint" $ do
    804       let src = T.unlines
    805             [ "_foo:"
    806             , "  adrp x8, _const@PAGE"
    807             , "  stlr x8, [sp]"
    808             , "  ldr x9, [sp]"
    809             , "  ldr x10, [x9]"
    810             , "  ret"
    811             ]
    812       case audit rt "test" src of
    813         Left e -> assertFailure $
    814           "parse failed: " ++ show e
    815         Right ar -> assertEqual "no violations"
    816           0 (length (arViolations ar))
    817 
    818   , testCase "good: stxr status register is public" $ do
    819       let src = T.unlines
    820             [ "_foo:"
    821             , "  stxr w0, x1, [x21]"
    822             , "  ldr x8, [x20, w0]"
    823             , "  ret"
    824             ]
    825       case audit rt "test" src of
    826         Left e -> assertFailure $
    827           "parse failed: " ++ show e
    828         Right ar -> assertEqual "no violations"
    829           0 (length (arViolations ar))
    830 
    831   , testCase "good: stlxr status register is public" $ do
    832       let src = T.unlines
    833             [ "_foo:"
    834             , "  stlxr w0, x1, [x21]"
    835             , "  ldr x8, [x20, w0]"
    836             , "  ret"
    837             ]
    838       case audit rt "test" src of
    839         Left e -> assertFailure $
    840           "parse failed: " ++ show e
    841         Right ar -> assertEqual "no violations"
    842           0 (length (arViolations ar))
    843   ]
    844 
    845 -- Inter-procedural tests
    846 
    847 interprocTests :: TestTree
    848 interprocTests = testGroup "InterProc" [
    849     testCase "interproc: callee sets x0 public" $ do
    850       let src = T.unlines
    851             [ "_callee:"
    852             , "  adrp x0, _const@PAGE"
    853             , "  ret"
    854             , "_caller:"
    855             , "  bl _callee"
    856             , "  ldr x1, [x0]"
    857             , "  ret"
    858             ]
    859       case audit rt "test" src of
    860         Left e -> assertFailure $
    861           "parse failed: " ++ show e
    862         Right ar -> assertEqual "default: 1 violation"
    863           1 (length (arViolations ar))
    864       case auditInterProc rt "test" src of
    865         Left e -> assertFailure $
    866           "parse failed: " ++ show e
    867         Right ar -> assertEqual "interproc: 0 violations"
    868           0 (length (arViolations ar))
    869 
    870   , testCase "interproc: callee leaves x0 unknown" $ do
    871       let src = T.unlines
    872             [ "_callee:"
    873             , "  ldr x0, [x21]"
    874             , "  ret"
    875             , "_caller:"
    876             , "  bl _callee"
    877             , "  ldr x1, [x0]"
    878             , "  ret"
    879             ]
    880       case audit rt "test" src of
    881         Left e -> assertFailure $
    882           "parse failed: " ++ show e
    883         Right ar -> assertEqual "default: 1 violation"
    884           1 (length (arViolations ar))
    885       case auditInterProc rt "test" src of
    886         Left e -> assertFailure $
    887           "parse failed: " ++ show e
    888         Right ar -> assertEqual "interproc: 1 violation"
    889           1 (length (arViolations ar))
    890 
    891   , testCase "interproc: default mode unchanged" $ do
    892       let src = T.unlines
    893             [ "foo:"
    894             , "  adrp x8, _const@PAGE"
    895             , "  bl bar"
    896             , "  ldr x0, [x8]"
    897             , "  ret"
    898             ]
    899       case audit rt "test" src of
    900         Left e -> assertFailure $
    901           "parse failed: " ++ show e
    902         Right ar -> assertEqual "1 violation"
    903           1 (length (arViolations ar))
    904   ]
    905 
    906 -- Provenance tests
    907 
    908 provenanceTests :: TestTree
    909 provenanceTests = testGroup "Provenance" [
    910     testCase "good: mov from public root preserves provenance" $ do
    911       let src = T.unlines
    912             [ "foo:"
    913             , "  mov x8, x20"
    914             , "  ldr x0, [x8]"
    915             , "  ret"
    916             ]
    917       case audit rt "test" src of
    918         Left e -> assertFailure $
    919           "parse failed: " ++ show e
    920         Right ar -> assertEqual "no violations"
    921           0 (length (arViolations ar))
    922 
    923   , testCase "good: add #imm preserves provenance" $ do
    924       let src = T.unlines
    925             [ "foo:"
    926             , "  add x8, x20, #16"
    927             , "  ldr x0, [x8]"
    928             , "  ret"
    929             ]
    930       case audit rt "test" src of
    931         Left e -> assertFailure $
    932           "parse failed: " ++ show e
    933         Right ar -> assertEqual "no violations"
    934           0 (length (arViolations ar))
    935 
    936   , testCase "bad: load clears provenance" $ do
    937       let src = T.unlines
    938             [ "foo:"
    939             , "  mov x8, x20"
    940             , "  ldr x8, [x8]"
    941             , "  ldr x0, [x8]"
    942             , "  ret"
    943             ]
    944       case audit rt "test" src of
    945         Left e -> assertFailure $
    946           "parse failed: " ++ show e
    947         Right ar -> do
    948           assertEqual "one violation"
    949             1 (length (arViolations ar))
    950           case arViolations ar of
    951             [v] -> case vReason v of
    952               UnknownBase X8 -> pure ()
    953               other -> assertFailure $
    954                 "wrong reason: " ++ show other
    955             _ -> assertFailure
    956               "expected one violation"
    957 
    958   , testCase "good: orr with xzr preserves provenance" $ do
    959       let src = T.unlines
    960             [ "foo:"
    961             , "  mov x8, x20"
    962             , "  orr x9, x8, xzr"
    963             , "  ldr x0, [x9]"
    964             , "  ret"
    965             ]
    966       case audit rt "test" src of
    967         Left e -> assertFailure $
    968           "parse failed: " ++ show e
    969         Right ar -> assertEqual "no violations"
    970           0 (length (arViolations ar))
    971 
    972   , testCase "good: pointer untagging preserves public provenance" $ do
    973       let src = T.unlines
    974             [ "foo:"
    975             , "  mov x8, x20"
    976             , "  and x8, x8, #0xfffffffffffffff8"
    977             , "  ldr x0, [x8]"
    978             , "  ret"
    979             ]
    980       case audit rt "test" src of
    981         Left e -> assertFailure $
    982           "parse failed: " ++ show e
    983         Right ar -> assertEqual "no violations"
    984           0 (length (arViolations ar))
    985 
    986   , testCase "bad: pointer untagging doesn't upgrade unknown provenance" $ do
    987       let src = T.unlines
    988             [ "foo:"
    989             , "  ldr x8, [x21]"
    990             , "  and x8, x8, #-8"
    991             , "  ldr x0, [x8]"
    992             , "  ret"
    993             ]
    994       case audit rt "test" src of
    995         Left e -> assertFailure $
    996           "parse failed: " ++ show e
    997         Right ar -> assertEqual "one violation"
    998           1 (length (arViolations ar))
    999 
   1000   , testCase "bad: scalar cannot be laundered via provenance" $ do
   1001       let src = T.unlines
   1002             [ "foo:"
   1003             , "  ldr x8, [x21]"
   1004             , "  add x9, x20, x8"
   1005             , "  ldr x0, [x9]"
   1006             , "  ret"
   1007             ]
   1008       case audit rt "test" src of
   1009         Left e -> assertFailure $
   1010           "parse failed: " ++ show e
   1011         Right ar -> do
   1012           assertEqual "one violation"
   1013             1 (length (arViolations ar))
   1014           case arViolations ar of
   1015             [v] -> case vReason v of
   1016               UnknownBase X9 -> pure ()
   1017               other -> assertFailure $
   1018                 "wrong reason: " ++ show other
   1019             _ -> assertFailure
   1020               "expected one violation"
   1021   ]
   1022 
   1023 -- Taint config tests
   1024 
   1025 taintConfigTests :: TestTree
   1026 taintConfigTests = testGroup "TaintConfig" [
   1027     testCase "parse valid config" $ do
   1028       let json = B8.pack
   1029             "{\"foo\": {\"secret\": [\"X0\"], \
   1030             \\"public\": [\"X1\"]}}"
   1031       case eitherDecodeStrict' json
   1032              :: Either String TaintConfig of
   1033         Left e -> assertFailure $
   1034           "parse failed: " ++ e
   1035         Right cfg -> do
   1036           let policy =
   1037                 Map.lookup "foo" (tcPolicies cfg)
   1038           case policy of
   1039             Nothing -> assertFailure
   1040               "missing policy for 'foo'"
   1041             Just p -> do
   1042               assertEqual "secret regs"
   1043                 (Set.singleton X0) (apSecret p)
   1044               assertEqual "public regs"
   1045                 (Set.singleton X1) (apPublic p)
   1046 
   1047   , testCase "parse invalid register" $ do
   1048       let json = B8.pack
   1049             "{\"foo\": {\"secret\": [\"INVALID\"]}}"
   1050       case eitherDecodeStrict' json
   1051              :: Either String TaintConfig of
   1052         Left _ -> pure ()
   1053         Right _ -> assertFailure
   1054           "should have failed on invalid register"
   1055 
   1056   , testCase "seedArgs marks secret" $ do
   1057       let policy = ArgPolicy (Set.singleton X0)
   1058             Set.empty Set.empty
   1059             Set.empty Set.empty
   1060           st = seedArgs rt policy (initTaintState rt)
   1061       assertEqual "X0 is secret"
   1062         Secret (getTaint X0 st)
   1063 
   1064   , testCase "seedArgs marks public" $ do
   1065       let policy = ArgPolicy Set.empty
   1066             (Set.singleton X0) Set.empty
   1067             Set.empty Set.empty
   1068           st = seedArgs rt policy (initTaintState rt)
   1069       assertEqual "X0 is public"
   1070         Public (getTaint X0 st)
   1071 
   1072   , testCase "secret takes precedence over public" $ do
   1073       let policy = ArgPolicy (Set.singleton X0)
   1074             (Set.singleton X0) Set.empty
   1075             Set.empty Set.empty
   1076           st = seedArgs rt policy (initTaintState rt)
   1077       assertEqual "X0 is secret"
   1078         Secret (getTaint X0 st)
   1079 
   1080   , testCase "secret_pointee load with imm offset produces Secret" $ do
   1081       let src = T.unlines
   1082             [ "_foo:"
   1083             , "  ldr x1, [x0, #8]"
   1084             , "  ldr x2, [x21, x1]"
   1085             , "  ret"
   1086             ]
   1087           cfg = TaintConfig (Map.singleton "_foo"
   1088             (ArgPolicy Set.empty Set.empty
   1089               (Set.singleton X0)
   1090               Set.empty Set.empty)) True
   1091       case auditWithConfig rt cfg "test" src of
   1092         Left e -> assertFailure $
   1093           "parse failed: " ++ show e
   1094         Right ar -> do
   1095           assertEqual "one violation"
   1096             1 (length (arViolations ar))
   1097           case arViolations ar of
   1098             [v] -> assertEqual "secret index"
   1099               (SecretIndex X1) (vReason v)
   1100             _ -> assertFailure
   1101               "expected exactly one violation"
   1102 
   1103   , testCase "secret_pointee load with reg offset produces Secret" $ do
   1104       let src = T.unlines
   1105             [ "_foo:"
   1106             , "  ldr x2, [x0, x1]"
   1107             , "  ldr x3, [x21, x2]"
   1108             , "  ret"
   1109             ]
   1110           cfg = TaintConfig (Map.singleton "_foo"
   1111             (ArgPolicy Set.empty Set.empty
   1112               (Set.singleton X0)
   1113               Set.empty Set.empty)) True
   1114       case auditWithConfig rt cfg "test" src of
   1115         Left e -> assertFailure $
   1116           "parse failed: " ++ show e
   1117         Right ar -> do
   1118           assertEqual "two violations"
   1119             2 (length (arViolations ar))
   1120           let reasons = map vReason (arViolations ar)
   1121           assertBool "has secret index x2"
   1122             (SecretIndex X2 `elem` reasons)
   1123 
   1124   , testCase "secret takes precedence over secret_pointee" $ do
   1125       let policy = ArgPolicy (Set.singleton X0)
   1126             Set.empty (Set.singleton X0)
   1127             Set.empty Set.empty
   1128           st = seedArgs rt policy (initTaintState rt)
   1129       assertEqual "X0 is secret"
   1130         Secret (getTaint X0 st)
   1131       assertEqual "X0 provenance"
   1132         ProvUnknown (getProvenance X0 st)
   1133 
   1134   , testCase "non-pointer arithmetic invalidates secret_pointee" $ do
   1135       let src = T.unlines
   1136             [ "_foo:"
   1137             , "  mul x0, x0, x1"
   1138             , "  ldr x2, [x0, #0]"
   1139             , "  ldr x3, [x21, x2]"
   1140             , "  ret"
   1141             ]
   1142           cfg = TaintConfig (Map.singleton "_foo"
   1143             (ArgPolicy Set.empty Set.empty
   1144               (Set.singleton X0)
   1145               Set.empty Set.empty)) True
   1146       case auditWithConfig rt cfg "test" src of
   1147         Left e -> assertFailure $
   1148           "parse failed: " ++ show e
   1149         Right ar ->
   1150           assertEqual "no secret violations" 0
   1151             (length $ filter isSecretViolation
   1152               (map vReason (arViolations ar)))
   1153 
   1154   , testCase "pointer arithmetic preserves secret_pointee" $ do
   1155       let src = T.unlines
   1156             [ "_foo:"
   1157             , "  add x0, x0, #8"
   1158             , "  ldr x1, [x0, #0]"
   1159             , "  ldr x2, [x21, x1]"
   1160             , "  ret"
   1161             ]
   1162           cfg = TaintConfig (Map.singleton "_foo"
   1163             (ArgPolicy Set.empty Set.empty
   1164               (Set.singleton X0)
   1165               Set.empty Set.empty)) True
   1166       case auditWithConfig rt cfg "test" src of
   1167         Left e -> assertFailure $
   1168           "parse failed: " ++ show e
   1169         Right ar ->
   1170           assertEqual "one secret violation" 1
   1171             (length $ filter isSecretViolation
   1172               (map vReason (arViolations ar)))
   1173 
   1174   , testCase "secret arg causes violation on indexed access" $ do
   1175       let src = T.unlines
   1176             [ "_mul_wnaf:"
   1177             , "  ldr x8, [x20, x0]"
   1178             , "  ret"
   1179             ]
   1180           cfg = TaintConfig (Map.singleton "_mul_wnaf"
   1181             (ArgPolicy (Set.singleton X0)
   1182               Set.empty Set.empty
   1183               Set.empty Set.empty)) True
   1184       case auditWithConfig rt cfg "test" src of
   1185         Left e -> assertFailure $
   1186           "parse failed: " ++ show e
   1187         Right ar -> do
   1188           assertEqual "one violation"
   1189             1 (length (arViolations ar))
   1190           case arViolations ar of
   1191             [v] -> case vReason v of
   1192               SecretIndex X0 -> pure ()
   1193               other -> assertFailure $
   1194                 "wrong reason: " ++ show other
   1195             _ -> assertFailure
   1196               "expected one violation"
   1197 
   1198   , testCase "public arg allows indexed access" $ do
   1199       let src = T.unlines
   1200             [ "_foo:"
   1201             , "  ldr x8, [x20, x0]"
   1202             , "  ret"
   1203             ]
   1204           cfg = TaintConfig (Map.singleton "_foo"
   1205             (ArgPolicy Set.empty (Set.singleton X0)
   1206               Set.empty Set.empty Set.empty)) True
   1207       case auditWithConfig rt cfg "test" src of
   1208         Left e -> assertFailure $
   1209           "parse failed: " ++ show e
   1210         Right ar -> assertEqual "no violations"
   1211           0 (length (arViolations ar))
   1212 
   1213   , testCase "interproc with config" $ do
   1214       let src = T.unlines
   1215             [ "_mul_wnaf:"
   1216             , "  ldr x8, [x20, x0]"
   1217             , "  ret"
   1218             ]
   1219           cfg = TaintConfig (Map.singleton "_mul_wnaf"
   1220             (ArgPolicy (Set.singleton X0)
   1221               Set.empty Set.empty
   1222               Set.empty Set.empty)) True
   1223       case auditInterProcWithConfig rt cfg "test" src of
   1224         Left e -> assertFailure $
   1225           "parse failed: " ++ show e
   1226         Right ar ->
   1227           assertEqual "one violation"
   1228             1 (length (arViolations ar))
   1229 
   1230   -- STG stack slot seeding tests
   1231   , testCase "parse stg_secret/stg_public in config" $ do
   1232       let json = B8.pack $ unlines
   1233             [ "{"
   1234             , "  \"_foo\": {"
   1235             , "    \"secret\": [\"X0\"],"
   1236             , "    \"stg_secret\": [8, 152],"
   1237             , "    \"stg_public\": [24]"
   1238             , "  }"
   1239             , "}"
   1240             ]
   1241       case eitherDecodeStrict' json
   1242              :: Either String TaintConfig of
   1243         Left e -> assertFailure $
   1244           "parse failed: " ++ e
   1245         Right cfg -> do
   1246           let policy =
   1247                 Map.lookup "_foo" (tcPolicies cfg)
   1248           case policy of
   1249             Nothing -> assertFailure
   1250               "missing policy for '_foo'"
   1251             Just p -> do
   1252               assertEqual "stg_secret"
   1253                 (Set.fromList [8, 152])
   1254                 (apStgSecret p)
   1255               assertEqual "stg_public"
   1256                 (Set.singleton 24)
   1257                 (apStgPublic p)
   1258 
   1259   , testCase "stg_secret seeds STG stack slot as secret" $ do
   1260       let src = T.unlines
   1261             [ "_foo:"
   1262             , "  ldr x8, [x20, #8]"
   1263             , "  ldr x9, [x21, x8]"
   1264             , "  ret"
   1265             ]
   1266           cfg = TaintConfig (Map.singleton "_foo"
   1267             (ArgPolicy Set.empty Set.empty Set.empty
   1268               (Set.singleton 8) Set.empty)) True
   1269       case auditWithConfig rt cfg "test" src of
   1270         Left e -> assertFailure $
   1271           "parse failed: " ++ show e
   1272         Right ar -> do
   1273           assertEqual "one violation"
   1274             1 (length (arViolations ar))
   1275           case arViolations ar of
   1276             [v] -> case vReason v of
   1277               SecretIndex X8 -> pure ()
   1278               other -> assertFailure $
   1279                 "wrong reason: " ++ show other
   1280             _ -> assertFailure
   1281               "expected one violation"
   1282 
   1283   , testCase "stg_public seeds STG stack slot as public" $ do
   1284       let src = T.unlines
   1285             [ "_foo:"
   1286             , "  ldr x8, [x20, #16]"
   1287             , "  ldr x9, [x21, x8]"
   1288             , "  ret"
   1289             ]
   1290           cfg = TaintConfig (Map.singleton "_foo"
   1291             (ArgPolicy Set.empty Set.empty Set.empty
   1292               Set.empty (Set.singleton 16))) True
   1293       case auditWithConfig rt cfg "test" src of
   1294         Left e -> assertFailure $
   1295           "parse failed: " ++ show e
   1296         Right ar -> assertEqual "no violations"
   1297           0 (length (arViolations ar))
   1298 
   1299   , testCase "stg_secret takes precedence over stg_public" $ do
   1300       let src = T.unlines
   1301             [ "_foo:"
   1302             , "  ldr x8, [x20, #8]"
   1303             , "  ldr x9, [x21, x8]"
   1304             , "  ret"
   1305             ]
   1306           cfg = TaintConfig (Map.singleton "_foo"
   1307             (ArgPolicy Set.empty Set.empty Set.empty
   1308               (Set.singleton 8)
   1309               (Set.singleton 8))) True
   1310       case auditWithConfig rt cfg "test" src of
   1311         Left e -> assertFailure $
   1312           "parse failed: " ++ show e
   1313         Right ar -> do
   1314           assertEqual "one violation"
   1315             1 (length (arViolations ar))
   1316           case arViolations ar of
   1317             [v] -> case vReason v of
   1318               SecretIndex X8 -> pure ()
   1319               other -> assertFailure $
   1320                 "wrong reason: " ++ show other
   1321             _ -> assertFailure
   1322               "expected one violation"
   1323 
   1324   -- STG stack delta tracking tests
   1325   , testCase "stg_secret preserved after sub x20" $ do
   1326       let src = T.unlines
   1327             [ "_foo:"
   1328             , "  sub x20, x20, #16"
   1329             , "  ldr x8, [x20, #24]"
   1330             , "  ldr x9, [x21, x8]"
   1331             , "  ret"
   1332             ]
   1333           cfg = TaintConfig (Map.singleton "_foo"
   1334             (ArgPolicy Set.empty Set.empty Set.empty
   1335               (Set.singleton 8) Set.empty)) True
   1336       case auditWithConfig rt cfg "test" src of
   1337         Left e -> assertFailure $
   1338           "parse failed: " ++ show e
   1339         Right ar -> do
   1340           assertEqual "one violation"
   1341             1 (length (arViolations ar))
   1342           case arViolations ar of
   1343             [v] -> case vReason v of
   1344               SecretIndex X8 -> pure ()
   1345               other -> assertFailure $
   1346                 "wrong reason: " ++ show other
   1347             _ -> assertFailure
   1348               "expected one violation"
   1349 
   1350   , testCase "stg_secret preserved after add x20" $ do
   1351       let src = T.unlines
   1352             [ "_foo:"
   1353             , "  add x20, x20, #16"
   1354             , "  ldr x8, [x20, #8]"
   1355             , "  ldr x9, [x21, x8]"
   1356             , "  ret"
   1357             ]
   1358           cfg = TaintConfig (Map.singleton "_foo"
   1359             (ArgPolicy Set.empty Set.empty Set.empty
   1360               (Set.singleton 24) Set.empty)) True
   1361       case auditWithConfig rt cfg "test" src of
   1362         Left e -> assertFailure $
   1363           "parse failed: " ++ show e
   1364         Right ar -> do
   1365           assertEqual "one violation"
   1366             1 (length (arViolations ar))
   1367           case arViolations ar of
   1368             [v] -> case vReason v of
   1369               SecretIndex X8 -> pure ()
   1370               other -> assertFailure $
   1371                 "wrong reason: " ++ show other
   1372             _ -> assertFailure
   1373               "expected one violation"
   1374 
   1375   , testCase "stg slot cleared on non-constant x20 update" $ do
   1376       let src = T.unlines
   1377             [ "_foo:"
   1378             , "  add x20, x20, x1"
   1379             , "  ldr x8, [x20, #8]"
   1380             , "  ldr x9, [x21, x8]"
   1381             , "  ret"
   1382             ]
   1383           cfg = TaintConfig (Map.singleton "_foo"
   1384             (ArgPolicy Set.empty Set.empty Set.empty
   1385               (Set.singleton 8) Set.empty)) True
   1386       case auditWithConfig rt cfg "test" src of
   1387         Left e -> assertFailure $
   1388           "parse failed: " ++ show e
   1389         Right ar ->
   1390           assertEqual "one violation"
   1391             1 (length (arViolations ar))
   1392   ]
   1393 
   1394 -- NCT scanner tests
   1395 
   1396 nctTests :: TestTree
   1397 nctTests = testGroup "NCT" [
   1398     testCase "cond branch: bcond" $ do
   1399       let src = T.unlines
   1400             [ "_foo:"
   1401             , "  cmp x0, x1"
   1402             , "  b.eq target"
   1403             , "target:"
   1404             , "  ret"
   1405             ]
   1406       case parseAsm src of
   1407         Left e -> assertFailure $
   1408           "parse failed: " ++ show e
   1409         Right lns -> do
   1410           let m = scanNct rt lns
   1411           case Map.lookup "_foo" m of
   1412             Nothing -> assertFailure
   1413               "no findings for _foo"
   1414             Just fs -> do
   1415               assertEqual "one finding"
   1416                 1 (length fs)
   1417               case fs of
   1418                 [f] -> assertEqual "reason"
   1419                   CondBranch (nctReason f)
   1420                 _ -> assertFailure
   1421                   "expected one finding"
   1422 
   1423   , testCase "cond branch: cbz" $ do
   1424       let src = "foo:\n  cbz x0, target\n\
   1425                 \target:\n  ret\n"
   1426       case parseAsm src of
   1427         Left e -> assertFailure $
   1428           "parse failed: " ++ show e
   1429         Right lns -> do
   1430           let m = scanNct rt lns
   1431           case Map.lookup "foo" m of
   1432             Nothing -> assertFailure "no findings"
   1433             Just fs -> case fs of
   1434               [f] -> assertEqual "reason"
   1435                 CondBranch (nctReason f)
   1436               _ -> assertFailure $
   1437                 "expected 1 finding, got "
   1438                 ++ show (length fs)
   1439 
   1440   , testCase "cond branch: tbz" $ do
   1441       let src = "foo:\n  tbz x0, #5, target\n\
   1442                 \target:\n  ret\n"
   1443       case parseAsm src of
   1444         Left e -> assertFailure $
   1445           "parse failed: " ++ show e
   1446         Right lns -> do
   1447           let m = scanNct rt lns
   1448           case Map.lookup "foo" m of
   1449             Nothing -> assertFailure "no findings"
   1450             Just fs -> case fs of
   1451               [f] -> assertEqual "reason"
   1452                 CondBranch (nctReason f)
   1453               _ -> assertFailure "expected 1 finding"
   1454 
   1455   , testCase "indirect branch: br" $ do
   1456       let src = "foo:\n  br x8\n"
   1457       case parseAsm src of
   1458         Left e -> assertFailure $
   1459           "parse failed: " ++ show e
   1460         Right lns -> do
   1461           let m = scanNct rt lns
   1462           case Map.lookup "foo" m of
   1463             Nothing -> assertFailure "no findings"
   1464             Just fs -> case fs of
   1465               [f] -> assertEqual "reason"
   1466                 IndirectBranch (nctReason f)
   1467               _ -> assertFailure "expected 1 finding"
   1468 
   1469   , testCase "indirect branch: blr" $ do
   1470       let src = "foo:\n  blr x8\n"
   1471       case parseAsm src of
   1472         Left e -> assertFailure $
   1473           "parse failed: " ++ show e
   1474         Right lns -> do
   1475           let m = scanNct rt lns
   1476           case Map.lookup "foo" m of
   1477             Nothing -> assertFailure "no findings"
   1478             Just fs -> case fs of
   1479               [f] -> assertEqual "reason"
   1480                 IndirectBranch (nctReason f)
   1481               _ -> assertFailure "expected 1 finding"
   1482 
   1483   , testCase "div: udiv" $ do
   1484       let src = "foo:\n  udiv x0, x1, x2\n"
   1485       case parseAsm src of
   1486         Left e -> assertFailure $
   1487           "parse failed: " ++ show e
   1488         Right lns -> do
   1489           let m = scanNct rt lns
   1490           case Map.lookup "foo" m of
   1491             Nothing -> assertFailure "no findings"
   1492             Just fs -> case fs of
   1493               [f] -> assertEqual "reason"
   1494                 Div (nctReason f)
   1495               _ -> assertFailure "expected 1 finding"
   1496 
   1497   , testCase "div: sdiv" $ do
   1498       let src = "foo:\n  sdiv x0, x1, x2\n"
   1499       case parseAsm src of
   1500         Left e -> assertFailure $
   1501           "parse failed: " ++ show e
   1502         Right lns -> do
   1503           let m = scanNct rt lns
   1504           case Map.lookup "foo" m of
   1505             Nothing -> assertFailure "no findings"
   1506             Just fs -> case fs of
   1507               [f] -> assertEqual "reason"
   1508                 Div (nctReason f)
   1509               _ -> assertFailure "expected 1 finding"
   1510 
   1511   , testCase "no finding: mul (DIT)" $ do
   1512       let src = "foo:\n  mul x0, x1, x2\n"
   1513       case parseAsm src of
   1514         Left e -> assertFailure $
   1515           "parse failed: " ++ show e
   1516         Right lns -> do
   1517           let m = scanNct rt lns
   1518           assertEqual "no findings" Nothing
   1519             (Map.lookup "foo" m)
   1520 
   1521   , testCase "no finding: madd (DIT)" $ do
   1522       let src = "foo:\n  madd x0, x1, x2, x3\n"
   1523       case parseAsm src of
   1524         Left e -> assertFailure $
   1525           "parse failed: " ++ show e
   1526         Right lns -> do
   1527           let m = scanNct rt lns
   1528           assertEqual "no findings" Nothing
   1529             (Map.lookup "foo" m)
   1530 
   1531   , testCase "no finding: umulh (DIT)" $ do
   1532       let src = "foo:\n  umulh x0, x1, x2\n"
   1533       case parseAsm src of
   1534         Left e -> assertFailure $
   1535           "parse failed: " ++ show e
   1536         Right lns -> do
   1537           let m = scanNct rt lns
   1538           assertEqual "no findings" Nothing
   1539             (Map.lookup "foo" m)
   1540 
   1541   , testCase "no finding: lsl with reg (DIT)" $ do
   1542       let src = "foo:\n  lsl x0, x1, x2\n"
   1543       case parseAsm src of
   1544         Left e -> assertFailure $
   1545           "parse failed: " ++ show e
   1546         Right lns -> do
   1547           let m = scanNct rt lns
   1548           assertEqual "no findings" Nothing
   1549             (Map.lookup "foo" m)
   1550 
   1551   , testCase "no finding: lsr with reg (DIT)" $ do
   1552       let src = "foo:\n  lsr x0, x1, x2\n"
   1553       case parseAsm src of
   1554         Left e -> assertFailure $
   1555           "parse failed: " ++ show e
   1556         Right lns -> do
   1557           let m = scanNct rt lns
   1558           assertEqual "no findings" Nothing
   1559             (Map.lookup "foo" m)
   1560 
   1561   , testCase "no finding: lsl with imm" $ do
   1562       let src = "foo:\n  lsl x0, x1, #5\n"
   1563       case parseAsm src of
   1564         Left e -> assertFailure $
   1565           "parse failed: " ++ show e
   1566         Right lns -> do
   1567           let m = scanNct rt lns
   1568           assertEqual "no findings" Nothing
   1569             (Map.lookup "foo" m)
   1570 
   1571   , testCase "reg-index: ldr [xN, xM]" $ do
   1572       let src = "foo:\n  ldr x0, [x1, x2]\n"
   1573       case parseAsm src of
   1574         Left e -> assertFailure $
   1575           "parse failed: " ++ show e
   1576         Right lns -> do
   1577           let m = scanNct rt lns
   1578           case Map.lookup "foo" m of
   1579             Nothing -> assertFailure "no findings"
   1580             Just fs -> case fs of
   1581               [f] -> assertEqual "reason"
   1582                 RegIndexAddr (nctReason f)
   1583               _ -> assertFailure "expected 1 finding"
   1584 
   1585   , testCase "reg-index: str [xN, xM, lsl #3]" $ do
   1586       let src = "foo:\n  str x0, [x1, x2, lsl #3]\n"
   1587       case parseAsm src of
   1588         Left e -> assertFailure $
   1589           "parse failed: " ++ show e
   1590         Right lns -> do
   1591           let m = scanNct rt lns
   1592           case Map.lookup "foo" m of
   1593             Nothing -> assertFailure "no findings"
   1594             Just fs -> case fs of
   1595               [f] -> assertEqual "reason"
   1596                 RegIndexAddr (nctReason f)
   1597               _ -> assertFailure "expected 1 finding"
   1598 
   1599   , testCase "no finding: ldr [xN, #imm]" $ do
   1600       let src = "foo:\n  ldr x0, [x1, #8]\n"
   1601       case parseAsm src of
   1602         Left e -> assertFailure $
   1603           "parse failed: " ++ show e
   1604         Right lns -> do
   1605           let m = scanNct rt lns
   1606           assertEqual "no findings" Nothing
   1607             (Map.lookup "foo" m)
   1608 
   1609   , testCase "grouping by function symbol" $ do
   1610       let src = T.unlines
   1611             [ "_foo:"
   1612             , "  cbz x0, L1"
   1613             , "L1:"
   1614             , "  ret"
   1615             , "_bar:"
   1616             , "  udiv x0, x1, x2"
   1617             , "  br x8"
   1618             , "  ret"
   1619             ]
   1620       case parseAsm src of
   1621         Left e -> assertFailure $
   1622           "parse failed: " ++ show e
   1623         Right lns -> do
   1624           let m = scanNct rt lns
   1625           assertEqual "foo: 1 finding" (Just 1)
   1626             (fmap length (Map.lookup "_foo" m))
   1627           assertEqual "bar: 2 findings" (Just 2)
   1628             (fmap length (Map.lookup "_bar" m))
   1629           assertEqual "L1 not a key" Nothing
   1630             (Map.lookup "L1" m)
   1631   ]
   1632 
   1633 -- Call graph tests
   1634 
   1635 callGraphTests :: TestTree
   1636 callGraphTests = testGroup "CallGraph" [
   1637     testCase "symbolExists: existing symbol" $ do
   1638       let src = T.unlines
   1639             [ "_foo:"
   1640             , "  ret"
   1641             ]
   1642       case parseAsm src of
   1643         Left e -> assertFailure $
   1644           "parse failed: " ++ show e
   1645         Right lns -> do
   1646           let cg = buildCallGraph rt lns
   1647           assertEqual "symbol exists"
   1648             True (symbolExists "_foo" cg)
   1649 
   1650   , testCase "symbolExists: missing symbol" $ do
   1651       let src = T.unlines
   1652             [ "_foo:"
   1653             , "  ret"
   1654             ]
   1655       case parseAsm src of
   1656         Left e -> assertFailure $
   1657           "parse failed: " ++ show e
   1658         Right lns -> do
   1659           let cg = buildCallGraph rt lns
   1660           assertEqual "symbol missing"
   1661             False (symbolExists "_bar" cg)
   1662 
   1663   , testCase "reachableSymbols: direct call" $ do
   1664       let src = T.unlines
   1665             [ "_foo:"
   1666             , "  bl _bar"
   1667             , "  ret"
   1668             , "_bar:"
   1669             , "  ret"
   1670             ]
   1671       case parseAsm src of
   1672         Left e -> assertFailure $
   1673           "parse failed: " ++ show e
   1674         Right lns -> do
   1675           let cg = buildCallGraph rt lns
   1676               reachable =
   1677                 reachableSymbols "_foo" cg
   1678           assertEqual "foo reachable" True
   1679             (Set.member "_foo" reachable)
   1680           assertEqual "bar reachable" True
   1681             (Set.member "_bar" reachable)
   1682           assertEqual "count" 2
   1683             (Set.size reachable)
   1684 
   1685   , testCase "reachableSymbols: transitive call" $ do
   1686       let src = T.unlines
   1687             [ "_foo:"
   1688             , "  bl _bar"
   1689             , "  ret"
   1690             , "_bar:"
   1691             , "  bl _baz"
   1692             , "  ret"
   1693             , "_baz:"
   1694             , "  ret"
   1695             ]
   1696       case parseAsm src of
   1697         Left e -> assertFailure $
   1698           "parse failed: " ++ show e
   1699         Right lns -> do
   1700           let cg = buildCallGraph rt lns
   1701               reachable =
   1702                 reachableSymbols "_foo" cg
   1703           assertEqual "baz reachable from foo" True
   1704             (Set.member "_baz" reachable)
   1705           assertEqual "count" 3
   1706             (Set.size reachable)
   1707 
   1708   , testCase "reachableSymbols: no callees" $ do
   1709       let src = T.unlines
   1710             [ "_foo:"
   1711             , "  ret"
   1712             , "_bar:"
   1713             , "  ret"
   1714             ]
   1715       case parseAsm src of
   1716         Left e -> assertFailure $
   1717           "parse failed: " ++ show e
   1718         Right lns -> do
   1719           let cg = buildCallGraph rt lns
   1720               reachable =
   1721                 reachableSymbols "_foo" cg
   1722           assertEqual "only foo reachable"
   1723             (Set.singleton "_foo") reachable
   1724 
   1725   , testCase "reachableSymbols: missing root returns empty" $ do
   1726       let src = T.unlines
   1727             [ "_foo:"
   1728             , "  ret"
   1729             ]
   1730       case parseAsm src of
   1731         Left e -> assertFailure $
   1732           "parse failed: " ++ show e
   1733         Right lns -> do
   1734           let cg = buildCallGraph rt lns
   1735               reachable =
   1736                 reachableSymbols "_missing" cg
   1737           assertEqual "empty set"
   1738             Set.empty reachable
   1739   ]
   1740 
   1741 -- Tail call inter-procedural tests
   1742 
   1743 tailCallTests :: TestTree
   1744 tailCallTests = testGroup "TailCall" [
   1745 
   1746     testCase "tail call propagates unknown taint" $ do
   1747       let src = T.unlines
   1748             [ "_caller:"
   1749             , "  ldr x23, [x21]"
   1750             , "  b _callee_info"
   1751             , "_callee_info:"
   1752             , "  ldr x0, [x20, x23]"
   1753             , "  ret"
   1754             ]
   1755       case auditInterProc rt "test" src of
   1756         Left e -> assertFailure $
   1757           "parse failed: " ++ show e
   1758         Right ar -> assertEqual "one violation"
   1759           1 (length (arViolations ar))
   1760 
   1761   , testCase "tail call propagates secret taint" $ do
   1762       let src = T.unlines
   1763             [ "_caller:"
   1764             , "  mov x22, x0"
   1765             , "  b _callee_info"
   1766             , "_callee_info:"
   1767             , "  ldr x1, [x20, x22]"
   1768             , "  ret"
   1769             ]
   1770           cfg = TaintConfig (Map.singleton "_caller"
   1771             (ArgPolicy (Set.singleton X0)
   1772               Set.empty Set.empty
   1773               Set.empty Set.empty)) True
   1774       case auditInterProcWithConfig rt cfg "test" src of
   1775         Left e -> assertFailure $
   1776           "parse failed: " ++ show e
   1777         Right ar ->
   1778           assertEqual "secret violation" 1
   1779             (length $ filter isSecretViolation
   1780               (map vReason (arViolations ar)))
   1781 
   1782   , testCase "local branch is intra-procedural" $ do
   1783       let src = T.unlines
   1784             [ "_foo:"
   1785             , "  mov x0, x1"
   1786             , "  b Llocal"
   1787             , "Llocal:"
   1788             , "  ldr x2, [x20, x0]"
   1789             , "  ret"
   1790             ]
   1791       case auditInterProc rt "test" src of
   1792         Left e -> assertFailure $
   1793           "parse failed: " ++ show e
   1794         Right ar -> assertEqual "one unknown violation"
   1795           1 (length (arViolations ar))
   1796 
   1797   , testCase "NCG local branch (Lc prefix) is intra-procedural" $ do
   1798       let src = T.unlines
   1799             [ "_foo:"
   1800             , "  mov x22, x0"
   1801             , "  b LcaB"
   1802             , "LcaB:"
   1803             , "  ldr x1, [x20, x22]"
   1804             , "  ret"
   1805             ]
   1806           cfg = TaintConfig (Map.singleton "_foo"
   1807             (ArgPolicy (Set.singleton X0)
   1808               Set.empty Set.empty
   1809               Set.empty Set.empty)) True
   1810       case auditInterProcWithConfig rt cfg "test" src of
   1811         Left e -> assertFailure $
   1812           "parse failed: " ++ show e
   1813         Right ar ->
   1814           assertEqual "secret violation" 1
   1815             (length $ filter isSecretViolation
   1816               (map vReason (arViolations ar)))
   1817 
   1818   , testCase "chain of tail calls propagates taint" $ do
   1819       let src = T.unlines
   1820             [ "_a:"
   1821             , "  mov x22, x0"
   1822             , "  b _b_info"
   1823             , "_b_info:"
   1824             , "  mov x23, x22"
   1825             , "  b _c_info"
   1826             , "_c_info:"
   1827             , "  ldr x1, [x20, x23]"
   1828             , "  ret"
   1829             ]
   1830           cfg = TaintConfig (Map.singleton "_a"
   1831             (ArgPolicy (Set.singleton X0)
   1832               Set.empty Set.empty
   1833               Set.empty Set.empty)) True
   1834       case auditInterProcWithConfig rt cfg "test" src of
   1835         Left e -> assertFailure $
   1836           "parse failed: " ++ show e
   1837         Right ar ->
   1838           assertEqual "secret violation" 1
   1839             (length $ filter isSecretViolation
   1840               (map vReason (arViolations ar)))
   1841 
   1842   , testCase "indirect call preserves callee-saved taint" $ do
   1843       let src = T.unlines
   1844             [ "_foo:"
   1845             , "  mov x23, x0"
   1846             , "  ldr x8, [x19]"
   1847             , "  blr x8"
   1848             , "  ldr x1, [x20, x23]"
   1849             , "  ret"
   1850             ]
   1851           cfg = TaintConfig (Map.singleton "_foo"
   1852             (ArgPolicy (Set.singleton X0)
   1853               Set.empty Set.empty
   1854               Set.empty Set.empty)) True
   1855       case auditInterProcWithConfig rt cfg "test" src of
   1856         Left e -> assertFailure $
   1857           "parse failed: " ++ show e
   1858         Right ar ->
   1859           assertEqual "x23 secret preserved" 1
   1860             (length $ filter isSecretViolation
   1861               (map vReason (arViolations ar)))
   1862 
   1863   , testCase "tail call to unknown function is conservative" $ do
   1864       let src = T.unlines
   1865             [ "_foo:"
   1866             , "  mov x22, x0"
   1867             , "  b _unknown_info"
   1868             ]
   1869           cfg = TaintConfig (Map.singleton "_foo"
   1870             (ArgPolicy (Set.singleton X0)
   1871               Set.empty Set.empty
   1872               Set.empty Set.empty)) True
   1873       case auditInterProcWithConfig rt cfg "test" src of
   1874         Left e -> assertFailure $
   1875           "parse failed: " ++ show e
   1876         Right ar -> assertEqual "no violations"
   1877           0 (length (arViolations ar))
   1878 
   1879   , testCase "secret_pointee propagates across tail call" $ do
   1880       let src = T.unlines
   1881             [ "_caller:"
   1882             , "  mov x22, x0"
   1883             , "  b _callee_info"
   1884             , "_callee_info:"
   1885             , "  ldr x1, [x22, #0]"
   1886             , "  ldr x2, [x20, x1]"
   1887             , "  ret"
   1888             ]
   1889           cfg = TaintConfig (Map.singleton "_caller"
   1890             (ArgPolicy Set.empty Set.empty
   1891               (Set.singleton X0)
   1892               Set.empty Set.empty)) True
   1893       case auditInterProcWithConfig rt cfg "test" src of
   1894         Left e -> assertFailure $
   1895           "parse failed: " ++ show e
   1896         Right ar ->
   1897           assertEqual "secret violation" 1
   1898             (length $ filter isSecretViolation
   1899               (map vReason (arViolations ar)))
   1900 
   1901   , testCase "mutual tail calls reach fixpoint" $ do
   1902       let src = T.unlines
   1903             [ "_a_info:"
   1904             , "  cbnz x0, Lskip"
   1905             , "  b _b_info"
   1906             , "Lskip:"
   1907             , "  ret"
   1908             , "_b_info:"
   1909             , "  ldr x1, [x20, x22]"
   1910             , "  b _a_info"
   1911             ]
   1912           cfg = TaintConfig (Map.singleton "_a_info"
   1913             (ArgPolicy (Set.singleton X22)
   1914               Set.empty Set.empty
   1915               Set.empty Set.empty)) True
   1916       case auditInterProcWithConfig rt cfg "test" src of
   1917         Left e -> assertFailure $
   1918           "parse failed: " ++ show e
   1919         Right ar ->
   1920           assertEqual "secret violation in cycle" 1
   1921             (length $ filter isSecretViolation
   1922               (map vReason (arViolations ar)))
   1923 
   1924   , testCase "bl followed by tail call" $ do
   1925       let src = T.unlines
   1926             [ "_caller:"
   1927             , "  mov x22, x0"
   1928             , "  bl _helper"
   1929             , "  b _callee_info"
   1930             , "_helper:"
   1931             , "  ret"
   1932             , "_callee_info:"
   1933             , "  ldr x1, [x20, x22]"
   1934             , "  ret"
   1935             ]
   1936           cfg = TaintConfig (Map.singleton "_caller"
   1937             (ArgPolicy (Set.singleton X0)
   1938               Set.empty Set.empty
   1939               Set.empty Set.empty)) True
   1940       case auditInterProcWithConfig rt cfg "test" src of
   1941         Left e -> assertFailure $
   1942           "parse failed: " ++ show e
   1943         Right ar ->
   1944           assertEqual "secret violation" 1
   1945             (length $ filter isSecretViolation
   1946               (map vReason (arViolations ar)))
   1947 
   1948   , testCase "tail call preserves x0 argument taint" $ do
   1949       let src = T.unlines
   1950             [ "_caller:"
   1951             , "  b _callee_info"
   1952             , "_callee_info:"
   1953             , "  ldr x1, [x20, x0]"
   1954             , "  ret"
   1955             ]
   1956           cfg = TaintConfig (Map.singleton "_caller"
   1957             (ArgPolicy (Set.singleton X0)
   1958               Set.empty Set.empty
   1959               Set.empty Set.empty)) True
   1960       case auditInterProcWithConfig rt cfg "test" src of
   1961         Left e -> assertFailure $
   1962           "parse failed: " ++ show e
   1963         Right ar ->
   1964           assertEqual "x0 secret preserved" 1
   1965             (length $ filter isSecretViolation
   1966               (map vReason (arViolations ar)))
   1967 
   1968   , testCase "external bl preserves callee-saved taint" $ do
   1969       let src = T.unlines
   1970             [ "_foo:"
   1971             , "  mov x23, x0"
   1972             , "  bl _external_func"
   1973             , "  ldr x1, [x20, x23]"
   1974             , "  ret"
   1975             ]
   1976           cfg = TaintConfig (Map.singleton "_foo"
   1977             (ArgPolicy (Set.singleton X0)
   1978               Set.empty Set.empty
   1979               Set.empty Set.empty)) True
   1980       case auditInterProcWithConfig rt cfg "test" src of
   1981         Left e -> assertFailure $
   1982           "parse failed: " ++ show e
   1983         Right ar ->
   1984           assertEqual
   1985             "x23 secret preserved across external call"
   1986             1
   1987             (length $ filter isSecretViolation
   1988               (map vReason (arViolations ar)))
   1989 
   1990   ]