summaryrefslogtreecommitdiffstats
path: root/chrome/common
diff options
context:
space:
mode:
authorabarth@chromium.org <abarth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-02 08:02:10 +0000
committerabarth@chromium.org <abarth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-02 08:02:10 +0000
commitee0be77ab281bdf7bf806a76ddf721abc8668864 (patch)
tree37335442b25c7929903f70063be196194664d6bd /chrome/common
parent69729cacd393118fa8e80fe30b8959df986435ab (diff)
downloadchromium_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.cc123
-rw-r--r--chrome/common/extensions/csp_validator.h33
-rw-r--r--chrome/common/extensions/csp_validator_unittest.cc74
-rw-r--r--chrome/common/extensions/extension.cc18
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).