Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(192)

Side by Side Diff: extensions/common/csp_validator.cc

Issue 747403002: Ignore insecure parts of CSP in extensions and allow extension to load (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix test expectations Created 5 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « extensions/common/csp_validator.h ('k') | extensions/common/csp_validator_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "extensions/common/csp_validator.h" 5 #include "extensions/common/csp_validator.h"
6 6
7 #include <vector> 7 #include <vector>
8 8
9 #include "base/strings/string_split.h" 9 #include "base/strings/string_split.h"
10 #include "base/strings/string_tokenizer.h" 10 #include "base/strings/string_tokenizer.h"
11 #include "base/strings/string_util.h" 11 #include "base/strings/string_util.h"
12 #include "content/public/common/url_constants.h" 12 #include "content/public/common/url_constants.h"
13 #include "extensions/common/constants.h" 13 #include "extensions/common/constants.h"
14 #include "extensions/common/error_utils.h"
15 #include "extensions/common/install_warning.h"
16 #include "extensions/common/manifest_constants.h"
14 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 17 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
15 18
16 namespace extensions { 19 namespace extensions {
17 20
18 namespace csp_validator { 21 namespace csp_validator {
19 22
20 namespace { 23 namespace {
21 24
22 const char kDefaultSrc[] = "default-src"; 25 const char kDefaultSrc[] = "default-src";
23 const char kScriptSrc[] = "script-src"; 26 const char kScriptSrc[] = "script-src";
24 const char kObjectSrc[] = "object-src"; 27 const char kObjectSrc[] = "object-src";
25 const char kPluginTypes[] = "plugin-types"; 28 const char kPluginTypes[] = "plugin-types";
26 29
30 const char kObjectSrcDefaultDirective[] = "object-src 'self';";
31 const char kScriptSrcDefaultDirective[] =
32 "script-src 'self' chrome-extension-resource:;";
33
27 const char kSandboxDirectiveName[] = "sandbox"; 34 const char kSandboxDirectiveName[] = "sandbox";
28 const char kAllowSameOriginToken[] = "allow-same-origin"; 35 const char kAllowSameOriginToken[] = "allow-same-origin";
29 const char kAllowTopNavigation[] = "allow-top-navigation"; 36 const char kAllowTopNavigation[] = "allow-top-navigation";
30 37
31 // This is the list of plugin types which are fully sandboxed and are safe to 38 // This is the list of plugin types which are fully sandboxed and are safe to
32 // load up in an extension, regardless of the URL they are navigated to. 39 // load up in an extension, regardless of the URL they are navigated to.
33 const char* const kSandboxedPluginTypes[] = { 40 const char* const kSandboxedPluginTypes[] = {
34 "application/pdf", 41 "application/pdf",
35 "application/x-google-chrome-pdf", 42 "application/x-google-chrome-pdf",
36 "application/x-pnacl" 43 "application/x-pnacl"
37 }; 44 };
38 45
39 struct DirectiveStatus { 46 struct DirectiveStatus {
40 explicit DirectiveStatus(const char* name) 47 explicit DirectiveStatus(const char* name)
41 : directive_name(name) 48 : directive_name(name), seen_in_policy(false) {}
42 , seen_in_policy(false)
43 , is_secure(false) {
44 }
45 49
46 const char* directive_name; 50 const char* directive_name;
47 bool seen_in_policy; 51 bool seen_in_policy;
48 bool is_secure;
49 }; 52 };
50 53
51 // Returns whether |url| starts with |scheme_and_separator| and does not have a 54 // Returns whether |url| starts with |scheme_and_separator| and does not have a
52 // too permissive wildcard host name. If |should_check_rcd| is true, then the 55 // too permissive wildcard host name. If |should_check_rcd| is true, then the
53 // Public suffix list is used to exclude wildcard TLDs such as "https://*.org". 56 // Public suffix list is used to exclude wildcard TLDs such as "https://*.org".
54 bool isNonWildcardTLD(const std::string& url, 57 bool isNonWildcardTLD(const std::string& url,
55 const std::string& scheme_and_separator, 58 const std::string& scheme_and_separator,
56 bool should_check_rcd) { 59 bool should_check_rcd) {
57 if (!StartsWithASCII(url, scheme_and_separator, true)) 60 if (!StartsWithASCII(url, scheme_and_separator, true))
58 return false; 61 return false;
59 62
60 size_t start_of_host = scheme_and_separator.length(); 63 size_t start_of_host = scheme_and_separator.length();
61 64
62 size_t end_of_host = url.find("/", start_of_host); 65 size_t end_of_host = url.find("/", start_of_host);
63 if (end_of_host == std::string::npos) 66 if (end_of_host == std::string::npos)
64 end_of_host = url.size(); 67 end_of_host = url.size();
65 68
66 // A missing host such as "chrome-extension://" is invalid, but for backwards-
67 // compatibility, accept such CSP parts. They will be ignored by Blink anyway.
68 // TODO(robwu): Remove this special case once crbug.com/434773 is fixed.
69 if (start_of_host == end_of_host)
70 return true;
71
72 // Note: It is sufficient to only compare the first character against '*' 69 // Note: It is sufficient to only compare the first character against '*'
73 // because the CSP only allows wildcards at the start of a directive, see 70 // because the CSP only allows wildcards at the start of a directive, see
74 // host-source and host-part at http://www.w3.org/TR/CSP2/#source-list-syntax 71 // host-source and host-part at http://www.w3.org/TR/CSP2/#source-list-syntax
75 bool is_wildcard_subdomain = end_of_host > start_of_host + 2 && 72 bool is_wildcard_subdomain = end_of_host > start_of_host + 2 &&
76 url[start_of_host] == '*' && url[start_of_host + 1] == '.'; 73 url[start_of_host] == '*' && url[start_of_host + 1] == '.';
77 if (is_wildcard_subdomain) 74 if (is_wildcard_subdomain)
78 start_of_host += 2; 75 start_of_host += 2;
79 76
80 size_t start_of_port = url.rfind(":", end_of_host); 77 size_t start_of_port = url.rfind(":", end_of_host);
81 // The ":" check at the end of the following condition is used to avoid 78 // The ":" check at the end of the following condition is used to avoid
(...skipping 26 matching lines...) Expand all
108 return true; 105 return true;
109 106
110 // Wildcards on subdomains of a TLD are not allowed. 107 // Wildcards on subdomains of a TLD are not allowed.
111 size_t registry_length = net::registry_controlled_domains::GetRegistryLength( 108 size_t registry_length = net::registry_controlled_domains::GetRegistryLength(
112 host, 109 host,
113 net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, 110 net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
114 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); 111 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
115 return registry_length != 0; 112 return registry_length != 0;
116 } 113 }
117 114
118 bool HasOnlySecureTokens(base::StringTokenizer& tokenizer, 115 InstallWarning CSPInstallWarning(const std::string& csp_warning) {
119 int options) { 116 return InstallWarning(csp_warning, manifest_keys::kContentSecurityPolicy);
120 while (tokenizer.GetNext()) { 117 }
121 std::string source = tokenizer.token(); 118
119 void GetSecureDirectiveValues(const std::string& directive_name,
120 base::StringTokenizer* tokenizer,
121 int options,
122 std::vector<std::string>* sane_csp_parts,
123 std::vector<InstallWarning>* warnings) {
124 sane_csp_parts->push_back(directive_name);
125 while (tokenizer->GetNext()) {
126 std::string source = tokenizer->token();
122 base::StringToLowerASCII(&source); 127 base::StringToLowerASCII(&source);
128 bool is_secure_csp_token = false;
123 129
124 // We might need to relax this whitelist over time. 130 // We might need to relax this whitelist over time.
125 if (source == "'self'" || 131 if (source == "'self'" ||
126 source == "'none'" || 132 source == "'none'" ||
127 source == "http://127.0.0.1" || 133 source == "http://127.0.0.1" ||
128 LowerCaseEqualsASCII(source, "blob:") || 134 LowerCaseEqualsASCII(source, "blob:") ||
129 LowerCaseEqualsASCII(source, "filesystem:") || 135 LowerCaseEqualsASCII(source, "filesystem:") ||
130 LowerCaseEqualsASCII(source, "http://localhost") || 136 LowerCaseEqualsASCII(source, "http://localhost") ||
131 StartsWithASCII(source, "http://127.0.0.1:", true) || 137 StartsWithASCII(source, "http://127.0.0.1:", true) ||
132 StartsWithASCII(source, "http://localhost:", true) || 138 StartsWithASCII(source, "http://localhost:", true) ||
133 isNonWildcardTLD(source, "https://", true) || 139 isNonWildcardTLD(source, "https://", true) ||
134 isNonWildcardTLD(source, "chrome://", false) || 140 isNonWildcardTLD(source, "chrome://", false) ||
135 isNonWildcardTLD(source, 141 isNonWildcardTLD(source,
136 std::string(extensions::kExtensionScheme) + 142 std::string(extensions::kExtensionScheme) +
137 url::kStandardSchemeSeparator, 143 url::kStandardSchemeSeparator,
138 false) || 144 false) ||
139 StartsWithASCII(source, "chrome-extension-resource:", true)) { 145 StartsWithASCII(source, "chrome-extension-resource:", true)) {
140 continue; 146 is_secure_csp_token = true;
147 } else if ((options & OPTIONS_ALLOW_UNSAFE_EVAL) &&
148 source == "'unsafe-eval'") {
149 is_secure_csp_token = true;
141 } 150 }
142 151
143 if (options & OPTIONS_ALLOW_UNSAFE_EVAL) { 152 if (is_secure_csp_token) {
144 if (source == "'unsafe-eval'") 153 sane_csp_parts->push_back(source);
145 continue; 154 } else if (warnings) {
155 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
156 manifest_errors::kInvalidCSPInsecureValue, source, directive_name)));
146 } 157 }
147
148 return false;
149 } 158 }
150 159 // End of CSP directive that was started at the beginning of this method. If
151 return true; // Empty values default to 'none', which is secure. 160 // none of the values are secure, the policy will be empty and default to
161 // 'none', which is secure.
162 sane_csp_parts->back().push_back(';');
152 } 163 }
153 164
154 // Returns true if |directive_name| matches |status.directive_name|. 165 // Returns true if |directive_name| matches |status.directive_name|.
155 bool UpdateStatus(const std::string& directive_name, 166 bool UpdateStatus(const std::string& directive_name,
156 base::StringTokenizer& tokenizer, 167 base::StringTokenizer* tokenizer,
157 DirectiveStatus* status, 168 DirectiveStatus* status,
158 int options) { 169 int options,
159 if (status->seen_in_policy) 170 std::vector<std::string>* sane_csp_parts,
160 return false; 171 std::vector<InstallWarning>* warnings) {
161 if (directive_name != status->directive_name) 172 if (directive_name != status->directive_name)
162 return false; 173 return false;
163 status->seen_in_policy = true; 174
164 status->is_secure = HasOnlySecureTokens(tokenizer, options); 175 if (!status->seen_in_policy) {
176 status->seen_in_policy = true;
177 GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts,
178 warnings);
179 } else {
180 // Don't show any errors for duplicate CSP directives, because it will be
181 // ignored by the CSP parser (http://www.w3.org/TR/CSP2/#policy-parsing).
182 GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts,
183 NULL);
184 }
165 return true; 185 return true;
166 } 186 }
167 187
168 // Parses the plugin-types directive and returns the list of mime types
169 // specified in |plugin_types|.
170 bool ParsePluginTypes(const std::string& directive_name,
171 base::StringTokenizer& tokenizer,
172 std::vector<std::string>* plugin_types) {
173 DCHECK(plugin_types);
174
175 if (directive_name != kPluginTypes || !plugin_types->empty())
176 return false;
177
178 while (tokenizer.GetNext()) {
179 std::string mime_type = tokenizer.token();
180 base::StringToLowerASCII(&mime_type);
181 // Since we're comparing the mime types to a whitelist, we don't check them
182 // for strict validity right now.
183 plugin_types->push_back(mime_type);
184 }
185
186 return true;
187 }
188
189 // Returns true if the |plugin_type| is one of the fully sandboxed plugin types. 188 // Returns true if the |plugin_type| is one of the fully sandboxed plugin types.
190 bool PluginTypeAllowed(const std::string& plugin_type) { 189 bool PluginTypeAllowed(const std::string& plugin_type) {
191 for (size_t i = 0; i < arraysize(kSandboxedPluginTypes); ++i) { 190 for (size_t i = 0; i < arraysize(kSandboxedPluginTypes); ++i) {
192 if (plugin_type == kSandboxedPluginTypes[i]) 191 if (plugin_type == kSandboxedPluginTypes[i])
193 return true; 192 return true;
194 } 193 }
195 return false; 194 return false;
196 } 195 }
197 196
198 // Returns true if the policy is allowed to contain an insecure object-src 197 // Returns true if the policy is allowed to contain an insecure object-src
199 // directive. This requires OPTIONS_ALLOW_INSECURE_OBJECT_SRC to be specified 198 // directive. This requires OPTIONS_ALLOW_INSECURE_OBJECT_SRC to be specified
200 // as an option and the plugin-types that can be loaded must be restricted to 199 // as an option and the plugin-types that can be loaded must be restricted to
201 // the set specified in kSandboxedPluginTypes. 200 // the set specified in kSandboxedPluginTypes.
202 bool AllowedToHaveInsecureObjectSrc( 201 bool AllowedToHaveInsecureObjectSrc(
203 int options, 202 int options,
204 const std::vector<std::string>& plugin_types) { 203 const std::vector<std::string>& directives) {
205 if (!(options & OPTIONS_ALLOW_INSECURE_OBJECT_SRC)) 204 if (!(options & OPTIONS_ALLOW_INSECURE_OBJECT_SRC))
206 return false; 205 return false;
207 206
208 // plugin-types must be specified. 207 for (size_t i = 0; i < directives.size(); ++i) {
209 if (plugin_types.empty()) 208 const std::string& input = directives[i];
210 return false; 209 base::StringTokenizer tokenizer(input, " \t\r\n");
211 210 if (!tokenizer.GetNext())
212 for (const auto& plugin_type : plugin_types) { 211 continue;
213 if (!PluginTypeAllowed(plugin_type)) 212 if (!LowerCaseEqualsASCII(tokenizer.token(), kPluginTypes))
214 return false; 213 continue;
214 while (tokenizer.GetNext()) {
215 if (!PluginTypeAllowed(tokenizer.token()))
216 return false;
217 }
218 // All listed plugin types are whitelisted.
219 return true;
215 } 220 }
216 221 // plugin-types not specified.
217 return true; 222 return false;
218 } 223 }
219 224
220 } // namespace 225 } // namespace
221 226
222 bool ContentSecurityPolicyIsLegal(const std::string& policy) { 227 bool ContentSecurityPolicyIsLegal(const std::string& policy) {
223 // We block these characters to prevent HTTP header injection when 228 // We block these characters to prevent HTTP header injection when
224 // representing the content security policy as an HTTP header. 229 // representing the content security policy as an HTTP header.
225 const char kBadChars[] = {',', '\r', '\n', '\0'}; 230 const char kBadChars[] = {',', '\r', '\n', '\0'};
226 231
227 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == 232 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) ==
228 std::string::npos; 233 std::string::npos;
229 } 234 }
230 235
231 bool ContentSecurityPolicyIsSecure(const std::string& policy, 236 std::string SanitizeContentSecurityPolicy(
232 int options) { 237 const std::string& policy,
238 int options,
239 std::vector<InstallWarning>* warnings) {
233 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 240 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
234 std::vector<std::string> directives; 241 std::vector<std::string> directives;
235 base::SplitString(policy, ';', &directives); 242 base::SplitString(policy, ';', &directives);
236 243
237 DirectiveStatus default_src_status(kDefaultSrc); 244 DirectiveStatus default_src_status(kDefaultSrc);
238 DirectiveStatus script_src_status(kScriptSrc); 245 DirectiveStatus script_src_status(kScriptSrc);
239 DirectiveStatus object_src_status(kObjectSrc); 246 DirectiveStatus object_src_status(kObjectSrc);
240 247
241 std::vector<std::string> plugin_types; 248 bool allow_insecure_object_src =
249 AllowedToHaveInsecureObjectSrc(options, directives);
242 250
251 std::vector<std::string> sane_csp_parts;
252 std::vector<InstallWarning> default_src_csp_warnings;
243 for (size_t i = 0; i < directives.size(); ++i) { 253 for (size_t i = 0; i < directives.size(); ++i) {
244 std::string& input = directives[i]; 254 std::string& input = directives[i];
245 base::StringTokenizer tokenizer(input, " \t\r\n"); 255 base::StringTokenizer tokenizer(input, " \t\r\n");
246 if (!tokenizer.GetNext()) 256 if (!tokenizer.GetNext())
247 continue; 257 continue;
248 258
249 std::string directive_name = tokenizer.token(); 259 std::string directive_name = tokenizer.token();
250 base::StringToLowerASCII(&directive_name); 260 base::StringToLowerASCII(&directive_name);
251 261
252 if (UpdateStatus(directive_name, tokenizer, &default_src_status, options)) 262 if (UpdateStatus(directive_name, &tokenizer, &default_src_status, options,
263 &sane_csp_parts, &default_src_csp_warnings))
253 continue; 264 continue;
254 if (UpdateStatus(directive_name, tokenizer, &script_src_status, options)) 265 if (UpdateStatus(directive_name, &tokenizer, &script_src_status, options,
266 &sane_csp_parts, warnings))
255 continue; 267 continue;
256 if (UpdateStatus(directive_name, tokenizer, &object_src_status, options)) 268 if (!allow_insecure_object_src &&
269 UpdateStatus(directive_name, &tokenizer, &object_src_status, options,
270 &sane_csp_parts, warnings))
257 continue; 271 continue;
258 if (ParsePluginTypes(directive_name, tokenizer, &plugin_types)) 272
259 continue; 273 // Pass the other CSP directives as-is without further validation.
274 sane_csp_parts.push_back(input + ";");
260 } 275 }
261 276
262 if (script_src_status.seen_in_policy && !script_src_status.is_secure) 277 if (default_src_status.seen_in_policy) {
263 return false; 278 if (!script_src_status.seen_in_policy ||
264 279 !object_src_status.seen_in_policy) {
265 if (object_src_status.seen_in_policy && !object_src_status.is_secure) { 280 // Insecure values in default-src are only relevant if either script-src
266 // Note that this does not fully check the object-src source list for 281 // or object-src is omitted.
267 // validity but Blink will do this anyway. 282 if (warnings)
268 if (!AllowedToHaveInsecureObjectSrc(options, plugin_types)) 283 warnings->insert(warnings->end(),
269 return false; 284 default_src_csp_warnings.begin(),
285 default_src_csp_warnings.end());
286 }
287 } else {
288 if (!script_src_status.seen_in_policy) {
289 sane_csp_parts.push_back(kScriptSrcDefaultDirective);
290 if (warnings)
291 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
292 manifest_errors::kInvalidCSPMissingSecureSrc, kScriptSrc)));
293 }
294 if (!object_src_status.seen_in_policy && !allow_insecure_object_src) {
295 sane_csp_parts.push_back(kObjectSrcDefaultDirective);
296 if (warnings)
297 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
298 manifest_errors::kInvalidCSPMissingSecureSrc, kObjectSrc)));
299 }
270 } 300 }
271 301
272 if (default_src_status.seen_in_policy && !default_src_status.is_secure) { 302 return JoinString(sane_csp_parts, ' ');
273 return script_src_status.seen_in_policy &&
274 object_src_status.seen_in_policy;
275 }
276
277 return default_src_status.seen_in_policy ||
278 (script_src_status.seen_in_policy && object_src_status.seen_in_policy);
279 } 303 }
280 304
281 bool ContentSecurityPolicyIsSandboxed( 305 bool ContentSecurityPolicyIsSandboxed(
282 const std::string& policy, Manifest::Type type) { 306 const std::string& policy, Manifest::Type type) {
283 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 307 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
284 std::vector<std::string> directives; 308 std::vector<std::string> directives;
285 base::SplitString(policy, ';', &directives); 309 base::SplitString(policy, ';', &directives);
286 310
287 bool seen_sandbox = false; 311 bool seen_sandbox = false;
288 312
(...skipping 26 matching lines...) Expand all
315 } 339 }
316 } 340 }
317 } 341 }
318 342
319 return seen_sandbox; 343 return seen_sandbox;
320 } 344 }
321 345
322 } // namespace csp_validator 346 } // namespace csp_validator
323 347
324 } // namespace extensions 348 } // namespace extensions
OLDNEW
« no previous file with comments | « extensions/common/csp_validator.h ('k') | extensions/common/csp_validator_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698