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