diff options
author | abarth@chromium.org <abarth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-02 08:02:10 +0000 |
---|---|---|
committer | abarth@chromium.org <abarth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-02 08:02:10 +0000 |
commit | ee0be77ab281bdf7bf806a76ddf721abc8668864 (patch) | |
tree | 37335442b25c7929903f70063be196194664d6bd /chrome/common | |
parent | 69729cacd393118fa8e80fe30b8959df986435ab (diff) | |
download | chromium_src-ee0be77ab281bdf7bf806a76ddf721abc8668864.zip chromium_src-ee0be77ab281bdf7bf806a76ddf721abc8668864.tar.gz chromium_src-ee0be77ab281bdf7bf806a76ddf721abc8668864.tar.bz2 |
Allow extenions to override the default content_security_policy, but require
the explicit policy to meet a minimum security threshold.
Review URL: http://codereview.chromium.org/8773028
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@112656 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r-- | chrome/common/extensions/csp_validator.cc | 123 | ||||
-rw-r--r-- | chrome/common/extensions/csp_validator.h | 33 | ||||
-rw-r--r-- | chrome/common/extensions/csp_validator_unittest.cc | 74 | ||||
-rw-r--r-- | chrome/common/extensions/extension.cc | 18 |
4 files changed, 242 insertions, 6 deletions
diff --git a/chrome/common/extensions/csp_validator.cc b/chrome/common/extensions/csp_validator.cc new file mode 100644 index 0000000..e80468b --- /dev/null +++ b/chrome/common/extensions/csp_validator.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/extensions/csp_validator.h" + +#include "base/string_split.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" + +namespace extensions { + +namespace csp_validator { + +namespace { + +const char kDefaultSrc[] = "default-src"; +const char kScriptSrc[] = "script-src"; +const char kObjectSrc[] = "object-src"; + +struct DirectiveStatus { + explicit DirectiveStatus(const char* name) + : directive_name(name) + , seen_in_policy(false) + , is_secure(false) { + } + + const char* directive_name; + bool seen_in_policy; + bool is_secure; +}; + +bool HasOnlySecureTokens(StringTokenizer& tokenizer) { + while (tokenizer.GetNext()) { + std::string source = tokenizer.token(); + StringToLowerASCII(&source); + + if (EndsWith(source, "*", true)) + return false; + + // We might need to relax this whitelist over time. + if (source == "'self'" || + source == "'none'" || + StartsWithASCII(source, "https://", true) || + StartsWithASCII(source, "chrome://", true) || + StartsWithASCII(source, "chrome-extension://", true)) { + continue; + } + + return false; + } + + return true; // Empty values default to 'none', which is secure. +} + +// Returns true if |directive_name| matches |status.directive_name|. +bool UpdateStatus(const std::string& directive_name, + StringTokenizer& tokenizer, + DirectiveStatus* status) { + if (status->seen_in_policy) + return false; + if (directive_name != status->directive_name) + return false; + status->seen_in_policy = true; + status->is_secure = HasOnlySecureTokens(tokenizer); + return true; +} + +} // namespace + +bool ContentSecurityPolicyIsLegal(const std::string& policy) { + // We block these characters to prevent HTTP header injection when + // representing the content security policy as an HTTP header. + const char kBadChars[] = {'\r', '\n', '\0'}; + + return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == + std::string::npos; +} + +bool ContentSecurityPolicyIsSecure(const std::string& policy) { + // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. + std::vector<std::string> directives; + base::SplitString(policy, ';', &directives); + + DirectiveStatus default_src_status(kDefaultSrc); + DirectiveStatus script_src_status(kScriptSrc); + DirectiveStatus object_src_status(kObjectSrc); + + for (size_t i = 0; i < directives.size(); ++i) { + std::string& input = directives[i]; + StringTokenizer tokenizer(input, " \t\r\n"); + if (!tokenizer.GetNext()) + continue; + + std::string directive_name = tokenizer.token(); + StringToLowerASCII(&directive_name); + + if (UpdateStatus(directive_name, tokenizer, &default_src_status)) + continue; + if (UpdateStatus(directive_name, tokenizer, &script_src_status)) + continue; + if (UpdateStatus(directive_name, tokenizer, &object_src_status)) + 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) + return false; + + 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; + } + + return default_src_status.seen_in_policy || + (script_src_status.seen_in_policy && object_src_status.seen_in_policy); +} + +} // csp_validator + +} // extensions diff --git a/chrome/common/extensions/csp_validator.h b/chrome/common/extensions/csp_validator.h new file mode 100644 index 0000000..640efd5 --- /dev/null +++ b/chrome/common/extensions/csp_validator.h @@ -0,0 +1,33 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_EXTENSIONS_CSP_VALIDATOR_H_ +#define CHROME_COMMON_EXTENSIONS_CSP_VALIDATOR_H_ +#pragma once + +#include <string> + +namespace extensions { + +namespace csp_validator { + +// Checks whether the given |policy| is legal for use in the extension system. +// This check just ensures that the policy doesn't contain any characters that +// will cause problems when we transmit the policy in an HTTP header. +bool ContentSecurityPolicyIsLegal(const std::string& policy); + +// Checks whether the given |policy| meets the minimum security requirements +// for use in the extension system. The philosophy behind our minimum +// requirements is that an XSS vulnerability in the extension should not be +// able to execute script, even in the precense of an active network attacker. +// Specifically, 'unsafe-inline' and 'unsafe-eval' are forbidden, as is +// script or object inclusion from insecure schemes. Also, the use of * is +// forbidden for scripts and objects. +bool ContentSecurityPolicyIsSecure(const std::string& policy); + +} // namespace csp_validator + +} // namespace extensions + +#endif // CHROME_COMMON_EXTENSIONS_CSP_VALIDATOR_H_ diff --git a/chrome/common/extensions/csp_validator_unittest.cc b/chrome/common/extensions/csp_validator_unittest.cc new file mode 100644 index 0000000..5d735fc --- /dev/null +++ b/chrome/common/extensions/csp_validator_unittest.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/extensions/csp_validator.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace extensions::csp_validator; + +TEST(ExtensionCSPValidator, IsLegal) { + EXPECT_TRUE(ContentSecurityPolicyIsLegal("foo")); + EXPECT_TRUE(ContentSecurityPolicyIsLegal( + "default-src 'self'; script-src http://www.google.com")); + EXPECT_FALSE(ContentSecurityPolicyIsLegal( + "default-src 'self';\nscript-src http://www.google.com")); + EXPECT_FALSE(ContentSecurityPolicyIsLegal( + "default-src 'self';\rscript-src http://www.google.com")); +} + +TEST(ExtensionCSPValidator, IsSecure) { + EXPECT_FALSE(ContentSecurityPolicyIsSecure("")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure("img-src https://google.com")); + + EXPECT_FALSE(ContentSecurityPolicyIsSecure("default-src *")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure("default-src 'self'")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure("default-src 'none'")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self' ftp://google.com")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src 'self' https://google.com")); + + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src *; default-src 'self'")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src 'self'; default-src *")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self'; default-src *; script-src *; script-src 'self'")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src 'self'; default-src *; script-src 'self'; script-src *")); + + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src *; script-src 'self'")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src *; script-src 'self'; img-src 'self'")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src *; script-src 'self'; object-src 'self'")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "script-src 'self'; object-src 'self'")); + + EXPECT_FALSE(ContentSecurityPolicyIsSecure("default-src 'unsafe-inline'")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure("default-src 'unsafe-eval'")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'unsafe-inline' 'none'")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self' http://google.com")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src 'self' https://google.com")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src 'self' chrome://resources")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src 'self' chrome-extension://aabbcc")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self' https:")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self' http:")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self' https://*")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self' *")); + EXPECT_FALSE(ContentSecurityPolicyIsSecure( + "default-src 'self' google.com")); + EXPECT_TRUE(ContentSecurityPolicyIsSecure( + "default-src 'self' https://*.google.com")); +} diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 8e25332..c5a5ea7 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -25,6 +25,7 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" +#include "chrome/common/extensions/csp_validator.h" #include "chrome/common/extensions/extension_action.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_utils.h" @@ -50,6 +51,9 @@ namespace keys = extension_manifest_keys; namespace values = extension_manifest_values; namespace errors = extension_manifest_errors; +using extensions::csp_validator::ContentSecurityPolicyIsLegal; +using extensions::csp_validator::ContentSecurityPolicyIsSecure; + namespace { const int kModernManifestVersion = 1; @@ -2240,21 +2244,23 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, *error = errors::kInvalidContentSecurityPolicy; return false; } - // We block these characters to prevent HTTP header injection when - // representing the content security policy as an HTTP header. - const char kBadCSPCharacters[] = {'\r', '\n', '\0'}; - if (content_security_policy.find_first_of(kBadCSPCharacters, 0, - arraysize(kBadCSPCharacters)) != - std::string::npos) { + if (!ContentSecurityPolicyIsLegal(content_security_policy)) { *error = errors::kInvalidContentSecurityPolicy; return false; } + if (manifest_version_ >= 2 && + !ContentSecurityPolicyIsSecure(content_security_policy)) { + *error = errors::kInvalidContentSecurityPolicy; + return false; + } + content_security_policy_ = content_security_policy; } else if (manifest_version_ >= 2) { // Manifest version 2 introduced a default Content-Security-Policy. // TODO(abarth): Should we continue to let extensions override the // default Content-Security-Policy? content_security_policy_ = kDefaultContentSecurityPolicy; + CHECK(ContentSecurityPolicyIsSecure(content_security_policy_)); } // Initialize devtools page url (optional). |