OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "core/frame/csp/CSPDirectiveList.h" | 5 #include "core/frame/csp/CSPDirectiveList.h" |
6 | 6 |
7 #include "core/frame/csp/ContentSecurityPolicy.h" | 7 #include "core/frame/csp/ContentSecurityPolicy.h" |
8 #include "core/frame/csp/SourceListDirective.h" | 8 #include "core/frame/csp/SourceListDirective.h" |
9 #include "platform/network/ContentSecurityPolicyParsers.h" | 9 #include "platform/network/ContentSecurityPolicyParsers.h" |
10 #include "platform/network/ResourceRequest.h" | 10 #include "platform/network/ResourceRequest.h" |
(...skipping 417 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
428 KURL resource = KURL(KURL(), "https://example.test/worker.js"); | 428 KURL resource = KURL(KURL(), "https://example.test/worker.js"); |
429 Member<CSPDirectiveList> directiveList = | 429 Member<CSPDirectiveList> directiveList = |
430 createList(test.list, ContentSecurityPolicyHeaderTypeEnforce); | 430 createList(test.list, ContentSecurityPolicyHeaderTypeEnforce); |
431 EXPECT_EQ(test.allowed, | 431 EXPECT_EQ(test.allowed, |
432 directiveList->allowWorkerFromSource( | 432 directiveList->allowWorkerFromSource( |
433 resource, ResourceRequest::RedirectStatus::NoRedirect, | 433 resource, ResourceRequest::RedirectStatus::NoRedirect, |
434 ContentSecurityPolicy::SuppressReport)); | 434 ContentSecurityPolicy::SuppressReport)); |
435 } | 435 } |
436 } | 436 } |
437 | 437 |
| 438 TEST_F(CSPDirectiveListTest, SubsumesBasedOnCSPSourcesOnly) { |
| 439 CSPDirectiveList* A = createList( |
| 440 "script-src http://*.one.com; img-src https://one.com " |
| 441 "http://two.com/imgs/", |
| 442 ContentSecurityPolicyHeaderTypeEnforce); |
| 443 |
| 444 struct TestCase { |
| 445 const std::vector<const char*> policies; |
| 446 bool expected; |
| 447 bool expectedFirstPolicyOpposite; |
| 448 } cases[] = { |
| 449 // `listB`, which is not as restrictive as `A`, is not subsumed. |
| 450 {{""}, false, true}, |
| 451 {{"script-src http://example.com"}, false, false}, |
| 452 {{"img-src http://example.com"}, false, false}, |
| 453 {{"script-src http://*.one.com"}, false, true}, |
| 454 {{"img-src https://one.com http://two.com/imgs/"}, false, true}, |
| 455 {{"default-src http://example.com"}, false, false}, |
| 456 {{"default-src https://one.com http://two.com/imgs/"}, false, false}, |
| 457 {{"default-src http://one.com"}, false, false}, |
| 458 {{"script-src http://*.one.com; img-src http://two.com/"}, false, false}, |
| 459 {{"script-src http://*.one.com", "img-src http://one.com"}, false, true}, |
| 460 {{"script-src http://*.one.com", "script-src https://two.com"}, |
| 461 false, |
| 462 true}, |
| 463 {{"script-src http://*.random.com", "script-src https://random.com"}, |
| 464 false, |
| 465 false}, |
| 466 {{"script-src http://one.com", "script-src https://random.com"}, |
| 467 false, |
| 468 false}, |
| 469 {{"script-src http://*.random.com; default-src http://one.com " |
| 470 "http://two.com/imgs/", |
| 471 "default-src https://random.com"}, |
| 472 false, |
| 473 false}, |
| 474 // `listB`, which is as restrictive as `A`, is subsumed. |
| 475 {{"default-src https://one.com"}, true, false}, |
| 476 {{"default-src http://random.com", |
| 477 "default-src https://non-random.com:*"}, |
| 478 true, |
| 479 false}, |
| 480 {{"script-src http://*.one.com; img-src https://one.com"}, true, false}, |
| 481 {{"script-src http://*.one.com; img-src https://one.com " |
| 482 "http://two.com/imgs/"}, |
| 483 true, |
| 484 true}, |
| 485 {{"script-src http://*.one.com", |
| 486 "img-src https://one.com http://two.com/imgs/"}, |
| 487 true, |
| 488 true}, |
| 489 {{"script-src http://*.random.com; default-src https://one.com " |
| 490 "http://two.com/imgs/", |
| 491 "default-src https://else.com"}, |
| 492 true, |
| 493 false}, |
| 494 {{"script-src http://*.random.com; default-src https://one.com " |
| 495 "http://two.com/imgs/", |
| 496 "default-src https://one.com"}, |
| 497 true, |
| 498 false}, |
| 499 }; |
| 500 |
| 501 CSPDirectiveList* emptyA = |
| 502 createList("", ContentSecurityPolicyHeaderTypeEnforce); |
| 503 |
| 504 for (const auto& test : cases) { |
| 505 HeapVector<Member<CSPDirectiveList>> listB; |
| 506 for (const auto& policy : test.policies) { |
| 507 listB.append(createList(policy, ContentSecurityPolicyHeaderTypeEnforce)); |
| 508 } |
| 509 |
| 510 EXPECT_EQ(test.expected, A->subsumes(listB)); |
| 511 // Empty CSPDirective subsumes any list. |
| 512 EXPECT_TRUE(emptyA->subsumes(listB)); |
| 513 // Check if first policy of `listB` subsumes `A`. |
| 514 EXPECT_EQ(test.expectedFirstPolicyOpposite, |
| 515 listB[0]->subsumes(HeapVector<Member<CSPDirectiveList>>(1, A))); |
| 516 } |
| 517 } |
| 518 |
| 519 TEST_F(CSPDirectiveListTest, OperativeDirectiveGivenType) { |
| 520 enum DefaultBehaviour { Default, NoDefault, ChildAndDefault }; |
| 521 |
| 522 struct TestCase { |
| 523 ContentSecurityPolicy::DirectiveType directive; |
| 524 const DefaultBehaviour type; |
| 525 } cases[] = { |
| 526 // Directives with default directive. |
| 527 {ContentSecurityPolicy::DirectiveType::ChildSrc, Default}, |
| 528 {ContentSecurityPolicy::DirectiveType::ConnectSrc, Default}, |
| 529 {ContentSecurityPolicy::DirectiveType::FontSrc, Default}, |
| 530 {ContentSecurityPolicy::DirectiveType::ImgSrc, Default}, |
| 531 {ContentSecurityPolicy::DirectiveType::ManifestSrc, Default}, |
| 532 {ContentSecurityPolicy::DirectiveType::MediaSrc, Default}, |
| 533 {ContentSecurityPolicy::DirectiveType::ObjectSrc, Default}, |
| 534 {ContentSecurityPolicy::DirectiveType::ScriptSrc, Default}, |
| 535 {ContentSecurityPolicy::DirectiveType::StyleSrc, Default}, |
| 536 // Directives with no default directive. |
| 537 {ContentSecurityPolicy::DirectiveType::BaseURI, NoDefault}, |
| 538 {ContentSecurityPolicy::DirectiveType::DefaultSrc, NoDefault}, |
| 539 {ContentSecurityPolicy::DirectiveType::FrameAncestors, NoDefault}, |
| 540 {ContentSecurityPolicy::DirectiveType::FormAction, NoDefault}, |
| 541 // Directive with multiple default directives. |
| 542 {ContentSecurityPolicy::DirectiveType::FrameSrc, ChildAndDefault}, |
| 543 {ContentSecurityPolicy::DirectiveType::WorkerSrc, ChildAndDefault}, |
| 544 }; |
| 545 |
| 546 // Initial set-up. |
| 547 std::stringstream allDirectives; |
| 548 for (const auto& test : cases) { |
| 549 const char* name = ContentSecurityPolicy::getDirectiveName(test.directive); |
| 550 allDirectives << name << " http://" << name << ".com; "; |
| 551 } |
| 552 CSPDirectiveList* allDirectivesList = createList( |
| 553 allDirectives.str().c_str(), ContentSecurityPolicyHeaderTypeEnforce); |
| 554 CSPDirectiveList* empty = |
| 555 createList("", ContentSecurityPolicyHeaderTypeEnforce); |
| 556 |
| 557 for (const auto& test : cases) { |
| 558 const char* name = ContentSecurityPolicy::getDirectiveName(test.directive); |
| 559 // When CSPDirectiveList is empty, then `null` should be returned for any |
| 560 // type. |
| 561 EXPECT_FALSE(empty->operativeDirective(test.directive)); |
| 562 |
| 563 // When all directives present, then given a type that directive value |
| 564 // should be returned. |
| 565 HeapVector<Member<CSPSource>> sources = |
| 566 allDirectivesList->operativeDirective(test.directive)->m_list; |
| 567 EXPECT_EQ(sources.size(), 1u); |
| 568 EXPECT_TRUE(sources[0]->m_host.startsWith(name)); |
| 569 |
| 570 std::stringstream allExceptThis; |
| 571 std::stringstream allExceptChildSrcAndThis; |
| 572 for (const auto& subtest : cases) { |
| 573 if (subtest.directive == test.directive) |
| 574 continue; |
| 575 const char* directiveName = |
| 576 ContentSecurityPolicy::getDirectiveName(subtest.directive); |
| 577 allExceptThis << directiveName << " http://" << directiveName << ".com; "; |
| 578 if (subtest.directive != ContentSecurityPolicy::DirectiveType::ChildSrc) { |
| 579 allExceptChildSrcAndThis << directiveName << " http://" << directiveName |
| 580 << ".com; "; |
| 581 } |
| 582 } |
| 583 CSPDirectiveList* allExceptThisList = createList( |
| 584 allExceptThis.str().c_str(), ContentSecurityPolicyHeaderTypeEnforce); |
| 585 CSPDirectiveList* allExceptChildSrcAndThisList = |
| 586 createList(allExceptChildSrcAndThis.str().c_str(), |
| 587 ContentSecurityPolicyHeaderTypeEnforce); |
| 588 |
| 589 switch (test.type) { |
| 590 case Default: |
| 591 sources = allExceptThisList->operativeDirective(test.directive)->m_list; |
| 592 EXPECT_EQ(sources.size(), 1u); |
| 593 EXPECT_EQ(sources[0]->m_host, "default-src.com"); |
| 594 break; |
| 595 case NoDefault: |
| 596 EXPECT_FALSE(allExceptThisList->operativeDirective(test.directive)); |
| 597 break; |
| 598 case ChildAndDefault: |
| 599 sources = allExceptThisList->operativeDirective(test.directive)->m_list; |
| 600 EXPECT_EQ(sources.size(), 1u); |
| 601 EXPECT_EQ(sources[0]->m_host, "child-src.com"); |
| 602 sources = |
| 603 allExceptChildSrcAndThisList->operativeDirective(test.directive) |
| 604 ->m_list; |
| 605 EXPECT_EQ(sources.size(), 1u); |
| 606 EXPECT_EQ(sources[0]->m_host, "default-src.com"); |
| 607 break; |
| 608 } |
| 609 } |
| 610 } |
| 611 |
| 612 TEST_F(CSPDirectiveListTest, GetSourceVector) { |
| 613 const std::vector<const char*> policies = { |
| 614 // Policy 1 |
| 615 "default-src https://default-src.com", |
| 616 // Policy 2 |
| 617 "child-src http://child-src.com", |
| 618 // Policy 3 |
| 619 "child-src http://child-src.com; default-src https://default-src.com", |
| 620 // Policy 4 |
| 621 "base-uri http://base-uri.com", |
| 622 // Policy 5 |
| 623 "frame-src http://frame-src.com"}; |
| 624 |
| 625 // Check expectations on the initial set-up. |
| 626 HeapVector<Member<CSPDirectiveList>> policyVector; |
| 627 for (const auto& policy : policies) { |
| 628 policyVector.append( |
| 629 createList(policy, ContentSecurityPolicyHeaderTypeEnforce)); |
| 630 } |
| 631 HeapVector<Member<SourceListDirective>> result = |
| 632 CSPDirectiveList::getSourceVector( |
| 633 ContentSecurityPolicy::DirectiveType::DefaultSrc, policyVector); |
| 634 EXPECT_EQ(result.size(), 2u); |
| 635 result = CSPDirectiveList::getSourceVector( |
| 636 ContentSecurityPolicy::DirectiveType::ChildSrc, policyVector); |
| 637 EXPECT_EQ(result.size(), 3u); |
| 638 result = CSPDirectiveList::getSourceVector( |
| 639 ContentSecurityPolicy::DirectiveType::BaseURI, policyVector); |
| 640 EXPECT_EQ(result.size(), 1u); |
| 641 result = CSPDirectiveList::getSourceVector( |
| 642 ContentSecurityPolicy::DirectiveType::FrameSrc, policyVector); |
| 643 EXPECT_EQ(result.size(), 4u); |
| 644 |
| 645 enum DefaultBehaviour { Default, NoDefault, ChildAndDefault }; |
| 646 |
| 647 struct TestCase { |
| 648 ContentSecurityPolicy::DirectiveType directive; |
| 649 const DefaultBehaviour type; |
| 650 size_t expectedTotal; |
| 651 int expectedCurrent; |
| 652 int expectedDefaultSrc; |
| 653 int expectedChildSrc; |
| 654 } cases[] = { |
| 655 // Directives with default directive. |
| 656 {ContentSecurityPolicy::DirectiveType::ChildSrc, Default, 4u, 3, 1, 3}, |
| 657 {ContentSecurityPolicy::DirectiveType::ConnectSrc, Default, 3u, 1, 2, 0}, |
| 658 {ContentSecurityPolicy::DirectiveType::FontSrc, Default, 3u, 1, 2, 0}, |
| 659 {ContentSecurityPolicy::DirectiveType::ImgSrc, Default, 3u, 1, 2, 0}, |
| 660 {ContentSecurityPolicy::DirectiveType::ManifestSrc, Default, 3u, 1, 2, 0}, |
| 661 {ContentSecurityPolicy::DirectiveType::MediaSrc, Default, 3u, 1, 2, 0}, |
| 662 {ContentSecurityPolicy::DirectiveType::ObjectSrc, Default, 3u, 1, 2, 0}, |
| 663 {ContentSecurityPolicy::DirectiveType::ScriptSrc, Default, 3u, 1, 2, 0}, |
| 664 {ContentSecurityPolicy::DirectiveType::StyleSrc, Default, 3u, 1, 2, 0}, |
| 665 // Directives with no default directive. |
| 666 {ContentSecurityPolicy::DirectiveType::BaseURI, NoDefault, 2u, 2, 0, 0}, |
| 667 {ContentSecurityPolicy::DirectiveType::FrameAncestors, NoDefault, 1u, 1, |
| 668 0, 0}, |
| 669 {ContentSecurityPolicy::DirectiveType::FormAction, NoDefault, 1u, 1, 0, |
| 670 0}, |
| 671 // Directive with multiple default directives. |
| 672 {ContentSecurityPolicy::DirectiveType::FrameSrc, ChildAndDefault, 5u, 2, |
| 673 1, 2}, |
| 674 }; |
| 675 |
| 676 for (const auto& test : cases) { |
| 677 // Initial set-up. |
| 678 HeapVector<Member<CSPDirectiveList>> policyVector; |
| 679 for (const auto& policy : policies) { |
| 680 policyVector.append( |
| 681 createList(policy, ContentSecurityPolicyHeaderTypeEnforce)); |
| 682 } |
| 683 // Append current test's policy. |
| 684 std::stringstream currentDirective; |
| 685 const char* name = ContentSecurityPolicy::getDirectiveName(test.directive); |
| 686 currentDirective << name << " http://" << name << ".com;"; |
| 687 policyVector.append(createList(currentDirective.str().c_str(), |
| 688 ContentSecurityPolicyHeaderTypeEnforce)); |
| 689 |
| 690 HeapVector<Member<SourceListDirective>> result = |
| 691 CSPDirectiveList::getSourceVector(test.directive, policyVector); |
| 692 |
| 693 EXPECT_EQ(result.size(), test.expectedTotal); |
| 694 |
| 695 int actualCurrent = 0, actualDefault = 0, actualChild = 0; |
| 696 for (const auto& srcList : result) { |
| 697 HeapVector<Member<CSPSource>> sources = srcList->m_list; |
| 698 for (const auto& source : sources) { |
| 699 if (source->m_host.startsWith(name)) |
| 700 actualCurrent += 1; |
| 701 else if (source->m_host == "default-src.com") |
| 702 actualDefault += 1; |
| 703 |
| 704 if (source->m_host == "child-src.com") |
| 705 actualChild += 1; |
| 706 } |
| 707 } |
| 708 |
| 709 EXPECT_EQ(actualDefault, test.expectedDefaultSrc); |
| 710 EXPECT_EQ(actualCurrent, test.expectedCurrent); |
| 711 EXPECT_EQ(actualChild, test.expectedChildSrc); |
| 712 |
| 713 // If another default-src is added that should only impact Fetch Directives |
| 714 policyVector.append(createList("default-src https://default-src.com;", |
| 715 ContentSecurityPolicyHeaderTypeEnforce)); |
| 716 size_t udpatedTotal = |
| 717 test.type != NoDefault ? test.expectedTotal + 1 : test.expectedTotal; |
| 718 EXPECT_EQ( |
| 719 CSPDirectiveList::getSourceVector(test.directive, policyVector).size(), |
| 720 udpatedTotal); |
| 721 size_t expectedChildSrc = |
| 722 test.directive == ContentSecurityPolicy::DirectiveType::ChildSrc ? 5u |
| 723 : 4u; |
| 724 EXPECT_EQ(CSPDirectiveList::getSourceVector( |
| 725 ContentSecurityPolicy::DirectiveType::ChildSrc, policyVector) |
| 726 .size(), |
| 727 expectedChildSrc); |
| 728 |
| 729 // If another child-src is added that should only impact frame-src and |
| 730 // child-src |
| 731 policyVector.append(createList("child-src http://child-src.com;", |
| 732 ContentSecurityPolicyHeaderTypeEnforce)); |
| 733 udpatedTotal = |
| 734 test.type == ChildAndDefault || |
| 735 test.directive == ContentSecurityPolicy::DirectiveType::ChildSrc |
| 736 ? udpatedTotal + 1 |
| 737 : udpatedTotal; |
| 738 EXPECT_EQ( |
| 739 CSPDirectiveList::getSourceVector(test.directive, policyVector).size(), |
| 740 udpatedTotal); |
| 741 expectedChildSrc = expectedChildSrc + 1u; |
| 742 EXPECT_EQ(CSPDirectiveList::getSourceVector( |
| 743 ContentSecurityPolicy::DirectiveType::ChildSrc, policyVector) |
| 744 .size(), |
| 745 expectedChildSrc); |
| 746 |
| 747 // If we add sandbox, nothing should change since it is currenly not |
| 748 // considered. |
| 749 policyVector.append(createList("sandbox http://sandbox.com;", |
| 750 ContentSecurityPolicyHeaderTypeEnforce)); |
| 751 EXPECT_EQ( |
| 752 CSPDirectiveList::getSourceVector(test.directive, policyVector).size(), |
| 753 udpatedTotal); |
| 754 EXPECT_EQ(CSPDirectiveList::getSourceVector( |
| 755 ContentSecurityPolicy::DirectiveType::ChildSrc, policyVector) |
| 756 .size(), |
| 757 expectedChildSrc); |
| 758 } |
| 759 } |
| 760 |
438 } // namespace blink | 761 } // namespace blink |
OLD | NEW |