Commit 82f74b02 authored by amalika's avatar amalika Committed by Commit bot

Part 3.5: Is policy list subsumed under subsuming policy?

This is part of an experimental feature Embedding-CSP.

Here we add support for `none` source lists. Note that
normalized returned CSP might not explicitly declare `none`, but
with contradictory sources can allow effectively `none`.

For example if the secure origin is `http://google.com`:
Content-Security-Policy: script-src 'self'
Content-Security-Policy: script-src https://example.test/

then it should be subsumed by the Embedding-CSP that is :
Content-Security-Policy: script-src 'none'

BUG=647588

Review-Url: https://codereview.chromium.org/2528423002
Cr-Commit-Position: refs/heads/master@{#436270}
parent d66f0fed
......@@ -1216,8 +1216,11 @@ SourceListDirectiveVector CSPDirectiveList::getSourceVector(
const CSPDirectiveListVector& policies) {
SourceListDirectiveVector sourceListDirectives;
for (const auto& policy : policies) {
if (SourceListDirective* directive = policy->operativeDirective(type))
if (SourceListDirective* directive = policy->operativeDirective(type)) {
if (directive->isNone())
return SourceListDirectiveVector(1, directive);
sourceListDirectives.append(directive);
}
}
return sourceListDirectives;
......@@ -1248,7 +1251,7 @@ bool CSPDirectiveList::subsumes(const CSPDirectiveListVector& other) {
// Embedding-CSP.
SourceListDirectiveVector requiredList =
getSourceVector(directive, CSPDirectiveListVector(1, this));
if (requiredList.size() == 0)
if (!requiredList.size())
continue;
SourceListDirective* required = requiredList[0];
// Aggregate all serialized source lists of the returned CSP into a vector
......
......@@ -18,6 +18,11 @@ class CSPDirectiveListTest : public ::testing::Test {
public:
CSPDirectiveListTest() : csp(ContentSecurityPolicy::create()) {}
virtual void SetUp() {
csp->setupSelf(
*SecurityOrigin::createFromString("https://example.test/image.png"));
}
CSPDirectiveList* createList(const String& list,
ContentSecurityPolicyHeaderType type) {
Vector<UChar> characters;
......@@ -516,6 +521,106 @@ TEST_F(CSPDirectiveListTest, SubsumesBasedOnCSPSourcesOnly) {
}
}
TEST_F(CSPDirectiveListTest, SubsumesIfNoneIsPresent) {
struct TestCase {
const char* policyA;
const std::vector<const char*> policiesB;
bool expected;
} cases[] = {
// `policyA` subsumes any vector of policies.
{"", {""}, true},
{"", {"script-src http://example.com"}, true},
{"", {"script-src 'none'"}, true},
{"", {"script-src http://*.one.com", "script-src https://two.com"}, true},
// `policyA` is 'none', but no policy in `policiesB` is.
{"script-src ", {""}, false},
{"script-src 'none'", {""}, false},
{"script-src ", {"script-src http://example.com"}, false},
{"script-src 'none'", {"script-src http://example.com"}, false},
{"script-src ", {"img-src 'none'"}, false},
{"script-src 'none'", {"img-src 'none'"}, false},
{"script-src ",
{"script-src http://*.one.com", "img-src https://two.com"},
false},
{"script-src 'none'",
{"script-src http://*.one.com", "img-src https://two.com"},
false},
{"script-src 'none'",
{"script-src http://*.one.com", "script-src https://two.com"},
true},
{"script-src 'none'",
{"script-src http://*.one.com", "script-src 'self'"},
true},
// `policyA` is not 'none', but at least effective result of `policiesB`
// is.
{"script-src http://example.com 'none'", {"script-src 'none'"}, true},
{"script-src http://example.com", {"script-src 'none'"}, true},
{"script-src http://example.com 'none'",
{"script-src http://*.one.com", "script-src http://one.com",
"script-src 'none'"},
true},
{"script-src http://example.com",
{"script-src http://*.one.com", "script-src http://one.com",
"script-src 'none'"},
true},
{"script-src http://one.com 'none'",
{"script-src http://*.one.com", "script-src http://one.com",
"script-src https://one.com"},
true},
// `policyA` is `none` and at least effective result of `policiesB` is
// too.
{"script-src ", {"script-src ", "script-src "}, true},
{"script-src 'none'", {"script-src", "script-src 'none'"}, true},
{"script-src ", {"script-src 'none'", "script-src 'none'"}, true},
{"script-src ",
{"script-src 'none' http://example.com",
"script-src 'none' http://example.com"},
false},
{"script-src 'none'", {"script-src 'none'", "script-src 'none'"}, true},
{"script-src 'none'",
{"script-src 'none'", "script-src 'none'", "script-src 'none'"},
true},
{"script-src 'none'",
{"script-src http://*.one.com", "script-src http://one.com",
"script-src 'none'"},
true},
{"script-src 'none'",
{"script-src http://*.one.com", "script-src http://two.com",
"script-src http://three.com"},
true},
// Policies contain special keywords.
{"script-src ", {"script-src ", "script-src 'unsafe-eval'"}, true},
{"script-src 'none'",
{"script-src 'unsafe-inline'", "script-src 'none'"},
true},
{"script-src ",
{"script-src 'none' 'unsafe-inline'",
"script-src 'none' 'unsafe-inline'"},
false},
{"script-src ",
{"script-src 'none' 'unsafe-inline'",
"script-src 'unsafe-inline' 'strict-dynamic'"},
false},
{"script-src 'unsafe-eval'",
{"script-src 'unsafe-eval'", "script 'unsafe-inline'"},
true},
{"script-src 'unsafe-inline'",
{"script-src ", "script http://example.com"},
true},
};
for (const auto& test : cases) {
CSPDirectiveList* A =
createList(test.policyA, ContentSecurityPolicyHeaderTypeEnforce);
HeapVector<Member<CSPDirectiveList>> listB;
for (const auto& policyB : test.policiesB)
listB.append(createList(policyB, ContentSecurityPolicyHeaderTypeEnforce));
EXPECT_EQ(test.expected, A->subsumes(listB));
}
}
TEST_F(CSPDirectiveListTest, OperativeDirectiveGivenType) {
enum DefaultBehaviour { Default, NoDefault, ChildAndDefault };
......
......@@ -103,6 +103,12 @@ bool SourceListDirective::allowHashedAttributes() const {
return m_allowHashedAttributes;
}
bool SourceListDirective::isNone() const {
return !m_list.size() && !m_allowSelf && !m_allowStar && !m_allowInline &&
!m_allowHashedAttributes && !m_allowEval && !m_allowDynamic &&
!m_nonces.size() && !m_hashes.size();
}
uint8_t SourceListDirective::hashAlgorithmsUsed() const {
return m_hashAlgorithmsUsed;
}
......@@ -595,8 +601,8 @@ bool SourceListDirective::allowAllInline() {
bool SourceListDirective::subsumes(
HeapVector<Member<SourceListDirective>> other) {
// TODO(amalika): Handle here special keywords.
if (!m_list.size() || !other.size())
return !m_list.size();
if (!other.size() || other[0]->isNone())
return other.size();
HeapVector<Member<CSPSource>> normalizedA = m_list;
if (m_allowSelf && other[0]->m_policy->getSelfSource())
......
......@@ -43,6 +43,7 @@ class CORE_EXPORT SourceListDirective final : public CSPDirective {
bool allowNonce(const String& nonce) const;
bool allowHash(const CSPHashValue&) const;
bool allowHashedAttributes() const;
bool isNone() const;
bool isHashOrNoncePresent() const;
uint8_t hashAlgorithmsUsed() const;
bool allowAllInline();
......
......@@ -438,12 +438,6 @@ TEST_F(SourceListDirectiveTest, Subsumes) {
}
EXPECT_EQ(required.subsumes(returned), test.expected);
// If required is empty, any returned should be subsumed by it.
SourceListDirective requiredIsEmpty("script-src", "", csp.get());
EXPECT_TRUE(
requiredIsEmpty.subsumes(HeapVector<Member<SourceListDirective>>()));
EXPECT_TRUE(requiredIsEmpty.subsumes(returned));
}
}
......@@ -819,4 +813,42 @@ TEST_F(SourceListDirectiveTest, SubsumesUnsafeAttributes) {
}
}
TEST_F(SourceListDirectiveTest, IsNone) {
struct TestCase {
String sources;
bool expected;
} cases[] = {
// Source list is 'none'.
{"'none'", true},
{"", true},
{" ", true},
// Source list is not 'none'.
{"http://example1.com/foo/", false},
{"'sha512-321cba'", false},
{"'nonce-yay'", false},
{"'strict-dynamic'", false},
{"'sha512-321cba' http://example1.com/foo/", false},
{"http://example1.com/foo/ 'sha512-321cba'", false},
{"http://example1.com/foo/ 'nonce-yay'", false},
{"'none' 'sha512-321cba' http://example1.com/foo/", false},
{"'none' http://example1.com/foo/ 'sha512-321cba'", false},
{"'none' http://example1.com/foo/ 'nonce-yay'", false},
{"'sha512-321cba' 'nonce-yay'", false},
{"http://example1.com/foo/ 'sha512-321cba' 'nonce-yay'", false},
{"http://example1.com/foo/ 'sha512-321cba' 'nonce-yay'", false},
{" 'sha512-321cba' 'nonce-yay' 'strict-dynamic'", false},
};
for (const auto& test : cases) {
SourceListDirective scriptSrc("script-src", test.sources, csp.get());
EXPECT_EQ(scriptSrc.isNone(), test.expected);
SourceListDirective styleSrc("form-action", test.sources, csp.get());
EXPECT_EQ(styleSrc.isNone(), test.expected);
SourceListDirective imgSrc("frame-src", test.sources, csp.get());
EXPECT_EQ(styleSrc.isNone(), test.expected);
}
}
} // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment