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 ]