Commit 4a09210e authored by Charlie Hu's avatar Charlie Hu Committed by Commit Bot

[Permissions-Policy] Use different error message prefix for Permissions-Policy header

Previously parse of 'Permissions-Policy' HTTP header still reports
parsing error with 'Error with Feature Policy: '.

This CL lets parse of 'Permissions-Policy' HTTP header use a separate
logger that has prefix 'Error with Permissions Policy: '.

Note:
Previously merge of feature policy and permissions policy was implicit,
i.e. done by concatenating the two together and relying on the parsing
rule that first occurrence of feature wins.
This CL makes the merge explicit in |FeaturePolicyParser::ParseHeader|.

Change-Id: I25f877899aa709705fb96b6a6cfe003dedd217d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2514961Reviewed-by: default avatarIan Clelland <iclelland@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Commit-Queue: Charlie Hu <chenleihu@google.com>
Cr-Commit-Position: refs/heads/master@{#826127}
parent e19c21a5
...@@ -148,7 +148,12 @@ void SecurityContextInit::ApplyFeaturePolicy( ...@@ -148,7 +148,12 @@ void SecurityContextInit::ApplyFeaturePolicy(
PolicyParserMessageBuffer feature_policy_logger( PolicyParserMessageBuffer feature_policy_logger(
"Error with Feature-Policy header: "); "Error with Feature-Policy header: ");
PolicyParserMessageBuffer report_only_feature_policy_logger( PolicyParserMessageBuffer report_only_feature_policy_logger(
"Error with Report-Only-Feature-Policy header: "); "Error with Feature-Policy-Report-Only header: ");
PolicyParserMessageBuffer permissions_policy_logger(
"Error with Permissions-Policy header: ");
PolicyParserMessageBuffer report_only_permissions_policy_logger(
"Error with Permissions-Policy-Report-Only header: ");
WTF::StringBuilder policy_builder; WTF::StringBuilder policy_builder;
policy_builder.Append(response.HttpHeaderField(http_names::kFeaturePolicy)); policy_builder.Append(response.HttpHeaderField(http_names::kFeaturePolicy));
...@@ -161,26 +166,28 @@ void SecurityContextInit::ApplyFeaturePolicy( ...@@ -161,26 +166,28 @@ void SecurityContextInit::ApplyFeaturePolicy(
feature_policy_header_ = FeaturePolicyParser::ParseHeader( feature_policy_header_ = FeaturePolicyParser::ParseHeader(
feature_policy_header, permissions_policy_header, feature_policy_header, permissions_policy_header,
execution_context_->GetSecurityOrigin(), feature_policy_logger, execution_context_->GetSecurityOrigin(), feature_policy_logger,
execution_context_); permissions_policy_logger, execution_context_);
ParsedFeaturePolicy report_only_feature_policy_header = ParsedFeaturePolicy report_only_feature_policy_header =
FeaturePolicyParser::ParseHeader( FeaturePolicyParser::ParseHeader(
response.HttpHeaderField(http_names::kFeaturePolicyReportOnly), response.HttpHeaderField(http_names::kFeaturePolicyReportOnly),
report_only_permissions_policy_header, report_only_permissions_policy_header,
execution_context_->GetSecurityOrigin(), execution_context_->GetSecurityOrigin(),
report_only_feature_policy_logger, execution_context_); report_only_feature_policy_logger,
report_only_permissions_policy_logger, execution_context_);
if (!report_only_feature_policy_header.empty()) { if (!report_only_feature_policy_header.empty()) {
UseCounter::Count(execution_context_, UseCounter::Count(execution_context_,
WebFeature::kFeaturePolicyReportOnlyHeader); WebFeature::kFeaturePolicyReportOnlyHeader);
} }
for (const auto& message : feature_policy_logger.GetMessages()) { auto messages = Vector<PolicyParserMessageBuffer::Message>();
execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( messages.AppendVector(feature_policy_logger.GetMessages());
mojom::blink::ConsoleMessageSource::kSecurity, message.level, messages.AppendVector(report_only_feature_policy_logger.GetMessages());
message.content)); messages.AppendVector(permissions_policy_logger.GetMessages());
} messages.AppendVector(report_only_permissions_policy_logger.GetMessages());
for (const auto& message : report_only_feature_policy_logger.GetMessages()) {
for (const auto& message : messages) {
execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity, message.level, mojom::blink::ConsoleMessageSource::kSecurity, message.level,
message.content)); message.content));
......
...@@ -20,7 +20,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { ...@@ -20,7 +20,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// TODO(csharrison): Be smarter about parsing this origin for performance. // TODO(csharrison): Be smarter about parsing this origin for performance.
scoped_refptr<const blink::SecurityOrigin> origin = scoped_refptr<const blink::SecurityOrigin> origin =
blink::SecurityOrigin::CreateFromString("https://example.com/"); blink::SecurityOrigin::CreateFromString("https://example.com/");
blink::FeaturePolicyParser::ParseHeader(WTF::String(data, size), blink::FeaturePolicyParser::ParseHeader(
g_empty_string, origin.get(), logger); WTF::String(data, size), g_empty_string, origin.get(), logger, logger);
return 0; return 0;
} }
...@@ -56,10 +56,12 @@ class CORE_EXPORT FeaturePolicyParser { ...@@ -56,10 +56,12 @@ class CORE_EXPORT FeaturePolicyParser {
// ExecutionContext is used to determine if any origin trials affect the // ExecutionContext is used to determine if any origin trials affect the
// parsing. Example of a feature policy string: // parsing. Example of a feature policy string:
// "vibrate a.com b.com; fullscreen 'none'; payment 'self', payment *". // "vibrate a.com b.com; fullscreen 'none'; payment 'self', payment *".
static ParsedFeaturePolicy ParseHeader(const String& feature_policy_header, static ParsedFeaturePolicy ParseHeader(
const String& feature_policy_header,
const String& permission_policy_header, const String& permission_policy_header,
scoped_refptr<const SecurityOrigin>, scoped_refptr<const SecurityOrigin>,
PolicyParserMessageBuffer& logger, PolicyParserMessageBuffer& feature_policy_logger,
PolicyParserMessageBuffer& permissions_policy_logger,
ExecutionContext* = nullptr); ExecutionContext* = nullptr);
// Converts a container policy string into a vector of allowlists, given self // Converts a container policy string into a vector of allowlists, given self
......
...@@ -106,7 +106,7 @@ class FeaturePolicyParserTest : public ::testing::Test { ...@@ -106,7 +106,7 @@ class FeaturePolicyParserTest : public ::testing::Test {
PolicyParserMessageBuffer& logger, PolicyParserMessageBuffer& logger,
ExecutionContext* context = nullptr) { ExecutionContext* context = nullptr) {
return FeaturePolicyParser::ParseHeader( return FeaturePolicyParser::ParseHeader(
feature_policy_header, g_empty_string, origin, logger, context); feature_policy_header, g_empty_string, origin, logger, logger, context);
} }
}; };
...@@ -193,6 +193,15 @@ class FeaturePolicyParserParsingTest ...@@ -193,6 +193,15 @@ class FeaturePolicyParserParsingTest
} }
} }
void CheckConsoleMessage(
const Vector<PolicyParserMessageBuffer::Message>& actual,
const std::vector<String> expected) {
ASSERT_EQ(actual.size(), expected.size());
for (size_t i = 0; i < actual.size(); ++i) {
EXPECT_EQ(actual[i].content, expected[i]);
}
}
public: public:
static const FeaturePolicyParserTestCase kCases[]; static const FeaturePolicyParserTestCase kCases[];
}; };
...@@ -550,16 +559,64 @@ TEST_P(FeaturePolicyParserParsingTest, PermissionsPolicyParsedCorrectly) { ...@@ -550,16 +559,64 @@ TEST_P(FeaturePolicyParserParsingTest, PermissionsPolicyParsedCorrectly) {
} }
TEST_F(FeaturePolicyParserParsingTest, TEST_F(FeaturePolicyParserParsingTest,
FeaturePolicyHeaderPermissionsPolicyHeaderCoExist) { FeaturePolicyDuplicatedFeatureDeclaration) {
PolicyParserMessageBuffer logger;
// For Feature-Policy header, if there are multiple declaration for same
// feature, the allowlist value from *FIRST* declaration will be taken.
CheckParsedPolicy(FeaturePolicyParser::ParseHeader(
"geolocation 'none', geolocation 'self'", "",
origin_a_.get(), logger, logger, nullptr /* context */),
{
{
// allowlist value 'none' is expected.
mojom::blink::FeaturePolicyFeature::kGeolocation,
/* matches_all_origins */ false,
/* matches_opaque_src */ false,
{},
},
});
EXPECT_TRUE(logger.GetMessages().IsEmpty());
}
TEST_F(FeaturePolicyParserParsingTest,
PermissionsPolicyDuplicatedFeatureDeclaration) {
PolicyParserMessageBuffer logger;
// For Permissions-Policy header, if there are multiple declaration for same
// feature, the allowlist value from *LAST* declaration will be taken.
CheckParsedPolicy(FeaturePolicyParser::ParseHeader(
"", "geolocation=(), geolocation=self", origin_a_.get(),
logger, logger, nullptr /* context */),
{
{
// allowlist value 'self' is expected.
mojom::blink::FeaturePolicyFeature::kGeolocation,
/* matches_all_origins */ false,
/* matches_opaque_src */ false,
{ORIGIN_A},
},
});
EXPECT_TRUE(logger.GetMessages().IsEmpty());
}
TEST_F(FeaturePolicyParserParsingTest,
FeaturePolicyHeaderPermissionsPolicyHeaderCoExistConflictEntry) {
PolicyParserMessageBuffer logger; PolicyParserMessageBuffer logger;
// When there is conflict take the value from permission policy.
// Non-conflicting entries will be merged. // When there is conflict take the value from permission policy,
// non-conflicting entries will be merged.
CheckParsedPolicy(FeaturePolicyParser::ParseHeader( CheckParsedPolicy(FeaturePolicyParser::ParseHeader(
"geolocation 'none', fullscreen 'self'", "geolocation 'none', fullscreen 'self'",
"geolocation=self, payment=*", origin_a_.get(), logger, "geolocation=self, payment=*", origin_a_.get(), logger,
nullptr /* context */), logger, nullptr /* context */),
{ {
{ {
// With geolocation appearing in both headers,
// the value should be taken from permissions policy
// header, which is 'self' here.
mojom::blink::FeaturePolicyFeature::kGeolocation, mojom::blink::FeaturePolicyFeature::kGeolocation,
/* matches_all_origins */ false, /* matches_all_origins */ false,
/* matches_opaque_src */ false, /* matches_opaque_src */ false,
...@@ -580,6 +637,49 @@ TEST_F(FeaturePolicyParserParsingTest, ...@@ -580,6 +637,49 @@ TEST_F(FeaturePolicyParserParsingTest,
}); });
} }
TEST_F(FeaturePolicyParserParsingTest,
FeaturePolicyHeaderPermissionsPolicyHeaderCoExistSeparateLogger) {
PolicyParserMessageBuffer feature_policy_logger("Feature Policy: ");
PolicyParserMessageBuffer permissions_policy_logger("Permissions Policy: ");
// 'geolocation' in permissions policy has a invalid allowlist item, which
// results in an empty allowlist, which is equivalent to 'none' in feature
// policy syntax.
CheckParsedPolicy(
FeaturePolicyParser::ParseHeader(
"worse-feature 'none', geolocation 'self'" /* feature_policy_header */
,
"bad-feature=*, geolocation=\"data://bad-origin\"" /* permissions_policy_header
*/
,
origin_a_.get(), feature_policy_logger, permissions_policy_logger,
nullptr /* context */
),
{
{
mojom::blink::FeaturePolicyFeature::kGeolocation,
/* matches_all_origins */ false,
/* matches_opaque_src */ false,
{},
},
});
CheckConsoleMessage(
feature_policy_logger.GetMessages(),
{
"Feature Policy: Unrecognized feature: 'worse-feature'.",
"Feature Policy: Feature geolocation has been specified in both "
"Feature-Policy and Permissions-Policy header. Value defined in "
"Permissions-Policy header will be used.",
});
CheckConsoleMessage(
permissions_policy_logger.GetMessages(),
{
"Permissions Policy: Unrecognized feature: 'bad-feature'.",
"Permissions Policy: Unrecognized origin: 'data://bad-origin'.",
});
}
TEST_F(FeaturePolicyParserTest, ParseValidPolicy) { TEST_F(FeaturePolicyParserTest, ParseValidPolicy) {
for (const char* policy_string : kValidPolicies) { for (const char* policy_string : kValidPolicies) {
PolicyParserMessageBuffer logger; PolicyParserMessageBuffer logger;
......
...@@ -21,6 +21,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { ...@@ -21,6 +21,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
scoped_refptr<const blink::SecurityOrigin> origin = scoped_refptr<const blink::SecurityOrigin> origin =
blink::SecurityOrigin::CreateFromString("https://example.com/"); blink::SecurityOrigin::CreateFromString("https://example.com/");
blink::FeaturePolicyParser::ParseHeader( blink::FeaturePolicyParser::ParseHeader(
g_empty_string, WTF::String(data, size), origin.get(), logger); g_empty_string, WTF::String(data, size), origin.get(), logger, logger);
return 0; return 0;
} }
...@@ -35,7 +35,7 @@ class PolicyTest : public testing::Test { ...@@ -35,7 +35,7 @@ class PolicyTest : public testing::Test {
"fullscreen *; payment 'self'; midi 'none'; camera 'self' " "fullscreen *; payment 'self'; midi 'none'; camera 'self' "
"https://example.com https://example.net", "https://example.com https://example.net",
/* permissions_policy_header */ g_empty_string, origin.get(), /* permissions_policy_header */ g_empty_string, origin.get(),
dummy_logger_); dummy_logger_, dummy_logger_);
feature_policy->SetHeaderPolicy(header); feature_policy->SetHeaderPolicy(header);
auto& security_context = auto& security_context =
......
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