| Index: extensions/common/csp_validator.cc
|
| diff --git a/extensions/common/csp_validator.cc b/extensions/common/csp_validator.cc
|
| index 6895b31fdb0e7b8f8cd219f8709db6f2dd4b2a7e..371d7f8d10a5ac764c7685e405517dfa4ef70c16 100644
|
| --- a/extensions/common/csp_validator.cc
|
| +++ b/extensions/common/csp_validator.cc
|
| @@ -11,6 +11,9 @@
|
| #include "base/strings/string_util.h"
|
| #include "content/public/common/url_constants.h"
|
| #include "extensions/common/constants.h"
|
| +#include "extensions/common/error_utils.h"
|
| +#include "extensions/common/install_warning.h"
|
| +#include "extensions/common/manifest_constants.h"
|
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
|
|
|
| namespace extensions {
|
| @@ -24,6 +27,10 @@ const char kScriptSrc[] = "script-src";
|
| const char kObjectSrc[] = "object-src";
|
| const char kPluginTypes[] = "plugin-types";
|
|
|
| +const char kObjectSrcDefaultDirective[] = "object-src 'self';";
|
| +const char kScriptSrcDefaultDirective[] =
|
| + "script-src 'self' chrome-extension-resource:;";
|
| +
|
| const char kSandboxDirectiveName[] = "sandbox";
|
| const char kAllowSameOriginToken[] = "allow-same-origin";
|
| const char kAllowTopNavigation[] = "allow-top-navigation";
|
| @@ -38,14 +45,10 @@ const char* const kSandboxedPluginTypes[] = {
|
|
|
| struct DirectiveStatus {
|
| explicit DirectiveStatus(const char* name)
|
| - : directive_name(name)
|
| - , seen_in_policy(false)
|
| - , is_secure(false) {
|
| - }
|
| + : directive_name(name), seen_in_policy(false) {}
|
|
|
| const char* directive_name;
|
| bool seen_in_policy;
|
| - bool is_secure;
|
| };
|
|
|
| // Returns whether |url| starts with |scheme_and_separator| and does not have a
|
| @@ -63,12 +66,6 @@ bool isNonWildcardTLD(const std::string& url,
|
| if (end_of_host == std::string::npos)
|
| end_of_host = url.size();
|
|
|
| - // A missing host such as "chrome-extension://" is invalid, but for backwards-
|
| - // compatibility, accept such CSP parts. They will be ignored by Blink anyway.
|
| - // TODO(robwu): Remove this special case once crbug.com/434773 is fixed.
|
| - if (start_of_host == end_of_host)
|
| - return true;
|
| -
|
| // Note: It is sufficient to only compare the first character against '*'
|
| // because the CSP only allows wildcards at the start of a directive, see
|
| // host-source and host-part at http://www.w3.org/TR/CSP2/#source-list-syntax
|
| @@ -115,11 +112,20 @@ bool isNonWildcardTLD(const std::string& url,
|
| return registry_length != 0;
|
| }
|
|
|
| -bool HasOnlySecureTokens(base::StringTokenizer& tokenizer,
|
| - int options) {
|
| - while (tokenizer.GetNext()) {
|
| - std::string source = tokenizer.token();
|
| +InstallWarning CSPInstallWarning(const std::string& csp_warning) {
|
| + return InstallWarning(csp_warning, manifest_keys::kContentSecurityPolicy);
|
| +}
|
| +
|
| +void GetSecureDirectiveValues(const std::string& directive_name,
|
| + base::StringTokenizer* tokenizer,
|
| + int options,
|
| + std::vector<std::string>* sane_csp_parts,
|
| + std::vector<InstallWarning>* warnings) {
|
| + sane_csp_parts->push_back(directive_name);
|
| + while (tokenizer->GetNext()) {
|
| + std::string source = tokenizer->token();
|
| base::StringToLowerASCII(&source);
|
| + bool is_secure_csp_token = false;
|
|
|
| // We might need to relax this whitelist over time.
|
| if (source == "'self'" ||
|
| @@ -137,52 +143,45 @@ bool HasOnlySecureTokens(base::StringTokenizer& tokenizer,
|
| url::kStandardSchemeSeparator,
|
| false) ||
|
| StartsWithASCII(source, "chrome-extension-resource:", true)) {
|
| - continue;
|
| + is_secure_csp_token = true;
|
| + } else if ((options & OPTIONS_ALLOW_UNSAFE_EVAL) &&
|
| + source == "'unsafe-eval'") {
|
| + is_secure_csp_token = true;
|
| }
|
|
|
| - if (options & OPTIONS_ALLOW_UNSAFE_EVAL) {
|
| - if (source == "'unsafe-eval'")
|
| - continue;
|
| + if (is_secure_csp_token) {
|
| + sane_csp_parts->push_back(source);
|
| + } else if (warnings) {
|
| + warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
|
| + manifest_errors::kInvalidCSPInsecureValue, source, directive_name)));
|
| }
|
| -
|
| - return false;
|
| }
|
| -
|
| - return true; // Empty values default to 'none', which is secure.
|
| + // End of CSP directive that was started at the beginning of this method. If
|
| + // none of the values are secure, the policy will be empty and default to
|
| + // 'none', which is secure.
|
| + sane_csp_parts->back().push_back(';');
|
| }
|
|
|
| // Returns true if |directive_name| matches |status.directive_name|.
|
| bool UpdateStatus(const std::string& directive_name,
|
| - base::StringTokenizer& tokenizer,
|
| + base::StringTokenizer* tokenizer,
|
| DirectiveStatus* status,
|
| - int options) {
|
| - if (status->seen_in_policy)
|
| - return false;
|
| + int options,
|
| + std::vector<std::string>* sane_csp_parts,
|
| + std::vector<InstallWarning>* warnings) {
|
| if (directive_name != status->directive_name)
|
| return false;
|
| - status->seen_in_policy = true;
|
| - status->is_secure = HasOnlySecureTokens(tokenizer, options);
|
| - return true;
|
| -}
|
| -
|
| -// Parses the plugin-types directive and returns the list of mime types
|
| -// specified in |plugin_types|.
|
| -bool ParsePluginTypes(const std::string& directive_name,
|
| - base::StringTokenizer& tokenizer,
|
| - std::vector<std::string>* plugin_types) {
|
| - DCHECK(plugin_types);
|
| -
|
| - if (directive_name != kPluginTypes || !plugin_types->empty())
|
| - return false;
|
|
|
| - while (tokenizer.GetNext()) {
|
| - std::string mime_type = tokenizer.token();
|
| - base::StringToLowerASCII(&mime_type);
|
| - // Since we're comparing the mime types to a whitelist, we don't check them
|
| - // for strict validity right now.
|
| - plugin_types->push_back(mime_type);
|
| + if (!status->seen_in_policy) {
|
| + status->seen_in_policy = true;
|
| + GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts,
|
| + warnings);
|
| + } else {
|
| + // Don't show any errors for duplicate CSP directives, because it will be
|
| + // ignored by the CSP parser (http://www.w3.org/TR/CSP2/#policy-parsing).
|
| + GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts,
|
| + NULL);
|
| }
|
| -
|
| return true;
|
| }
|
|
|
| @@ -201,20 +200,26 @@ bool PluginTypeAllowed(const std::string& plugin_type) {
|
| // the set specified in kSandboxedPluginTypes.
|
| bool AllowedToHaveInsecureObjectSrc(
|
| int options,
|
| - const std::vector<std::string>& plugin_types) {
|
| + const std::vector<std::string>& directives) {
|
| if (!(options & OPTIONS_ALLOW_INSECURE_OBJECT_SRC))
|
| return false;
|
|
|
| - // plugin-types must be specified.
|
| - if (plugin_types.empty())
|
| - return false;
|
| -
|
| - for (const auto& plugin_type : plugin_types) {
|
| - if (!PluginTypeAllowed(plugin_type))
|
| - return false;
|
| + for (size_t i = 0; i < directives.size(); ++i) {
|
| + const std::string& input = directives[i];
|
| + base::StringTokenizer tokenizer(input, " \t\r\n");
|
| + if (!tokenizer.GetNext())
|
| + continue;
|
| + if (!LowerCaseEqualsASCII(tokenizer.token(), kPluginTypes))
|
| + continue;
|
| + while (tokenizer.GetNext()) {
|
| + if (!PluginTypeAllowed(tokenizer.token()))
|
| + return false;
|
| + }
|
| + // All listed plugin types are whitelisted.
|
| + return true;
|
| }
|
| -
|
| - return true;
|
| + // plugin-types not specified.
|
| + return false;
|
| }
|
|
|
| } // namespace
|
| @@ -228,8 +233,10 @@ bool ContentSecurityPolicyIsLegal(const std::string& policy) {
|
| std::string::npos;
|
| }
|
|
|
| -bool ContentSecurityPolicyIsSecure(const std::string& policy,
|
| - int options) {
|
| +std::string SanitizeContentSecurityPolicy(
|
| + const std::string& policy,
|
| + int options,
|
| + std::vector<InstallWarning>* warnings) {
|
| // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
|
| std::vector<std::string> directives;
|
| base::SplitString(policy, ';', &directives);
|
| @@ -238,8 +245,11 @@ bool ContentSecurityPolicyIsSecure(const std::string& policy,
|
| DirectiveStatus script_src_status(kScriptSrc);
|
| DirectiveStatus object_src_status(kObjectSrc);
|
|
|
| - std::vector<std::string> plugin_types;
|
| + bool allow_insecure_object_src =
|
| + AllowedToHaveInsecureObjectSrc(options, directives);
|
|
|
| + std::vector<std::string> sane_csp_parts;
|
| + std::vector<InstallWarning> default_src_csp_warnings;
|
| for (size_t i = 0; i < directives.size(); ++i) {
|
| std::string& input = directives[i];
|
| base::StringTokenizer tokenizer(input, " \t\r\n");
|
| @@ -249,33 +259,47 @@ bool ContentSecurityPolicyIsSecure(const std::string& policy,
|
| std::string directive_name = tokenizer.token();
|
| base::StringToLowerASCII(&directive_name);
|
|
|
| - if (UpdateStatus(directive_name, tokenizer, &default_src_status, options))
|
| - continue;
|
| - if (UpdateStatus(directive_name, tokenizer, &script_src_status, options))
|
| + if (UpdateStatus(directive_name, &tokenizer, &default_src_status, options,
|
| + &sane_csp_parts, &default_src_csp_warnings))
|
| continue;
|
| - if (UpdateStatus(directive_name, tokenizer, &object_src_status, options))
|
| + if (UpdateStatus(directive_name, &tokenizer, &script_src_status, options,
|
| + &sane_csp_parts, warnings))
|
| continue;
|
| - if (ParsePluginTypes(directive_name, tokenizer, &plugin_types))
|
| + if (!allow_insecure_object_src &&
|
| + UpdateStatus(directive_name, &tokenizer, &object_src_status, options,
|
| + &sane_csp_parts, warnings))
|
| continue;
|
| - }
|
|
|
| - if (script_src_status.seen_in_policy && !script_src_status.is_secure)
|
| - return false;
|
| -
|
| - if (object_src_status.seen_in_policy && !object_src_status.is_secure) {
|
| - // Note that this does not fully check the object-src source list for
|
| - // validity but Blink will do this anyway.
|
| - if (!AllowedToHaveInsecureObjectSrc(options, plugin_types))
|
| - return false;
|
| + // Pass the other CSP directives as-is without further validation.
|
| + sane_csp_parts.push_back(input + ";");
|
| }
|
|
|
| - if (default_src_status.seen_in_policy && !default_src_status.is_secure) {
|
| - return script_src_status.seen_in_policy &&
|
| - object_src_status.seen_in_policy;
|
| + if (default_src_status.seen_in_policy) {
|
| + if (!script_src_status.seen_in_policy ||
|
| + !object_src_status.seen_in_policy) {
|
| + // Insecure values in default-src are only relevant if either script-src
|
| + // or object-src is omitted.
|
| + if (warnings)
|
| + warnings->insert(warnings->end(),
|
| + default_src_csp_warnings.begin(),
|
| + default_src_csp_warnings.end());
|
| + }
|
| + } else {
|
| + if (!script_src_status.seen_in_policy) {
|
| + sane_csp_parts.push_back(kScriptSrcDefaultDirective);
|
| + if (warnings)
|
| + warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
|
| + manifest_errors::kInvalidCSPMissingSecureSrc, kScriptSrc)));
|
| + }
|
| + if (!object_src_status.seen_in_policy && !allow_insecure_object_src) {
|
| + sane_csp_parts.push_back(kObjectSrcDefaultDirective);
|
| + if (warnings)
|
| + warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
|
| + manifest_errors::kInvalidCSPMissingSecureSrc, kObjectSrc)));
|
| + }
|
| }
|
|
|
| - return default_src_status.seen_in_policy ||
|
| - (script_src_status.seen_in_policy && object_src_status.seen_in_policy);
|
| + return JoinString(sane_csp_parts, ' ');
|
| }
|
|
|
| bool ContentSecurityPolicyIsSandboxed(
|
|
|