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

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

Issue 2563843002: Restrict app sandbox's CSP to disallow loading web content in them. (Closed)
Patch Set: address comments + rework CL + StringPieces Created 4 years 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
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 <stddef.h> 7 #include <stddef.h>
8 8
9 #include <vector> 9 #include <vector>
10 10
11 #include "base/bind.h"
12 #include "base/callback.h"
11 #include "base/macros.h" 13 #include "base/macros.h"
12 #include "base/strings/string_split.h" 14 #include "base/strings/string_split.h"
13 #include "base/strings/string_tokenizer.h" 15 #include "base/strings/string_tokenizer.h"
14 #include "base/strings/string_util.h" 16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
15 #include "content/public/common/url_constants.h" 18 #include "content/public/common/url_constants.h"
16 #include "extensions/common/constants.h" 19 #include "extensions/common/constants.h"
17 #include "extensions/common/error_utils.h" 20 #include "extensions/common/error_utils.h"
18 #include "extensions/common/install_warning.h" 21 #include "extensions/common/install_warning.h"
19 #include "extensions/common/manifest_constants.h" 22 #include "extensions/common/manifest_constants.h"
20 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 23 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
21 24
22 namespace extensions { 25 namespace extensions {
23 26
24 namespace csp_validator { 27 namespace csp_validator {
25 28
26 namespace { 29 namespace {
27 30
28 const char kDefaultSrc[] = "default-src"; 31 const char kDefaultSrc[] = "default-src";
29 const char kScriptSrc[] = "script-src"; 32 const char kScriptSrc[] = "script-src";
30 const char kObjectSrc[] = "object-src"; 33 const char kObjectSrc[] = "object-src";
34 const char kFrameSrc[] = "frame-src";
35 const char kChildSrc[] = "child-src";
36
37 const char kDirectiveSeparator = ';';
38
31 const char kPluginTypes[] = "plugin-types"; 39 const char kPluginTypes[] = "plugin-types";
32 40
33 const char kObjectSrcDefaultDirective[] = "object-src 'self';"; 41 const char kObjectSrcDefaultDirective[] = "object-src 'self';";
34 const char kScriptSrcDefaultDirective[] = 42 const char kScriptSrcDefaultDirective[] =
35 "script-src 'self' chrome-extension-resource:;"; 43 "script-src 'self' chrome-extension-resource:;";
36 44
45 const char kAppSandboxSubframeSrcDefaultDirective[] = "child-src 'self';";
46 const char kAppSandboxScriptSrcDefaultDirective[] =
47 "script-src 'self' 'unsafe-inline' 'unsafe-eval';";
48
37 const char kSandboxDirectiveName[] = "sandbox"; 49 const char kSandboxDirectiveName[] = "sandbox";
38 const char kAllowSameOriginToken[] = "allow-same-origin"; 50 const char kAllowSameOriginToken[] = "allow-same-origin";
39 const char kAllowTopNavigation[] = "allow-top-navigation"; 51 const char kAllowTopNavigation[] = "allow-top-navigation";
40 52
41 // This is the list of plugin types which are fully sandboxed and are safe to 53 // This is the list of plugin types which are fully sandboxed and are safe to
42 // load up in an extension, regardless of the URL they are navigated to. 54 // load up in an extension, regardless of the URL they are navigated to.
43 const char* const kSandboxedPluginTypes[] = { 55 const char* const kSandboxedPluginTypes[] = {
44 "application/pdf", 56 "application/pdf",
45 "application/x-google-chrome-pdf", 57 "application/x-google-chrome-pdf",
46 "application/x-pnacl" 58 "application/x-pnacl"
47 }; 59 };
48 60
49 // List of CSP hash-source prefixes that are accepted. Blink is a bit more 61 // List of CSP hash-source prefixes that are accepted. Blink is a bit more
50 // lenient, but we only accept standard hashes to be forward-compatible. 62 // lenient, but we only accept standard hashes to be forward-compatible.
51 // http://www.w3.org/TR/2015/CR-CSP2-20150721/#hash_algo 63 // http://www.w3.org/TR/2015/CR-CSP2-20150721/#hash_algo
52 const char* const kHashSourcePrefixes[] = { 64 const char* const kHashSourcePrefixes[] = {
53 "'sha256-", 65 "'sha256-",
54 "'sha384-", 66 "'sha384-",
55 "'sha512-" 67 "'sha512-"
56 }; 68 };
57 69
58 struct DirectiveStatus { 70 class DirectiveStatus {
Devlin 2016/12/20 17:36:38 Class comments
lazyboy 2016/12/22 03:07:30 Done.
59 explicit DirectiveStatus(const char* name) 71 public:
60 : directive_name(name), seen_in_policy(false) {} 72 DirectiveStatus(const std::string& name) { directive_names_.push_back(name); }
Devlin 2016/12/20 17:36:38 Could we just do: DirectiveStatus(std::initializer
lazyboy 2016/12/22 03:07:29 Nice, done.
73 // Subframe related directives can have multiple directive names: "child-src"
74 // or "frame-src".
75 DirectiveStatus(const std::string& name1, const std::string& name2) {
76 directive_names_.push_back(name1);
77 directive_names_.push_back(name2);
78 }
79 bool Matches(const std::string& directive_name) const {
Devlin 2016/12/20 17:36:38 \n function comments
lazyboy 2016/12/22 03:07:30 Done.
80 for (const auto& directive : directive_names_) {
81 if (!base::CompareCaseInsensitiveASCII(directive_name, directive))
82 return true;
83 }
84 return false;
85 }
Devlin 2016/12/20 17:36:39 \n
lazyboy 2016/12/22 03:07:29 Done.
86 bool seen_in_policy() const { return seen_in_policy_; }
87 void set_seen_in_policy() { seen_in_policy_ = true; }
61 88
62 const char* directive_name; 89 std::string name() const {
63 bool seen_in_policy; 90 DCHECK(!directive_names_.empty());
91 return directive_names_[0];
92 }
93
94 private:
95 std::vector<std::string> directive_names_;
96 bool seen_in_policy_ = false;
Devlin 2016/12/20 17:36:38 if we don't need to copy this, DISALLOW_COPY_AND_A
Devlin 2016/12/20 17:36:39 member comments
lazyboy 2016/12/22 03:07:29 Done.
lazyboy 2016/12/22 03:07:30 Done.
64 }; 97 };
65 98
66 // Returns whether |url| starts with |scheme_and_separator| and does not have a 99 // Returns whether |url| starts with |scheme_and_separator| and does not have a
67 // too permissive wildcard host name. If |should_check_rcd| is true, then the 100 // too permissive wildcard host name. If |should_check_rcd| is true, then the
68 // Public suffix list is used to exclude wildcard TLDs such as "https://*.org". 101 // Public suffix list is used to exclude wildcard TLDs such as "https://*.org".
69 bool isNonWildcardTLD(const std::string& url, 102 bool isNonWildcardTLD(const std::string& url,
70 const std::string& scheme_and_separator, 103 const std::string& scheme_and_separator,
71 bool should_check_rcd) { 104 bool should_check_rcd) {
72 if (!base::StartsWith(url, scheme_and_separator, 105 if (!base::StartsWith(url, scheme_and_separator,
73 base::CompareCase::SENSITIVE)) 106 base::CompareCase::SENSITIVE))
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
117 if (host == "googleapis.com") 150 if (host == "googleapis.com")
118 return true; 151 return true;
119 152
120 // Wildcards on subdomains of a TLD are not allowed. 153 // Wildcards on subdomains of a TLD are not allowed.
121 return net::registry_controlled_domains::HostHasRegistryControlledDomain( 154 return net::registry_controlled_domains::HostHasRegistryControlledDomain(
122 host, net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, 155 host, net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
123 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); 156 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
124 } 157 }
125 158
126 // Checks whether the source is a syntactically valid hash. 159 // Checks whether the source is a syntactically valid hash.
127 bool IsHashSource(const std::string& source) { 160 bool IsHashSource(const base::StringPiece& source) {
Devlin 2016/12/20 17:36:38 usually we don't pass StringPiece by const&, becau
lazyboy 2016/12/22 03:07:30 Done.
128 size_t hash_end = source.length() - 1; 161 size_t hash_end = source.length() - 1;
129 if (source.empty() || source[hash_end] != '\'') { 162 if (source.empty() || source[hash_end] != '\'') {
Devlin 2016/12/20 17:36:38 since you're here, source.back()?
lazyboy 2016/12/22 03:07:29 Done.
130 return false; 163 return false;
131 } 164 }
132 165
133 for (const char* prefix : kHashSourcePrefixes) { 166 for (const char* prefix : kHashSourcePrefixes) {
134 if (base::StartsWith(source, prefix, 167 if (base::StartsWith(source, prefix,
135 base::CompareCase::INSENSITIVE_ASCII)) { 168 base::CompareCase::INSENSITIVE_ASCII)) {
136 for (size_t i = strlen(prefix); i < hash_end; ++i) { 169 for (size_t i = strlen(prefix); i < hash_end; ++i) {
137 const char c = source[i]; 170 const char c = source[i];
138 // The hash must be base64-encoded. Do not allow any other characters. 171 // The hash must be base64-encoded. Do not allow any other characters.
139 if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && c != '+' && 172 if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && c != '+' &&
140 c != '/' && c != '=') { 173 c != '/' && c != '=') {
141 return false; 174 return false;
142 } 175 }
143 } 176 }
144 return true; 177 return true;
145 } 178 }
146 } 179 }
147 return false; 180 return false;
148 } 181 }
149 182
150 InstallWarning CSPInstallWarning(const std::string& csp_warning) { 183 InstallWarning CSPInstallWarning(const std::string& csp_warning) {
151 return InstallWarning(csp_warning, manifest_keys::kContentSecurityPolicy); 184 return InstallWarning(csp_warning, manifest_keys::kContentSecurityPolicy);
152 } 185 }
153 186
154 void GetSecureDirectiveValues(const std::string& directive_name, 187 std::string GetSecureDirectiveValues(
155 base::StringTokenizer* tokenizer, 188 int options,
156 int options, 189 const std::string& directive_name,
157 std::vector<std::string>* sane_csp_parts, 190 const std::vector<base::StringPiece>& directive_values,
158 std::vector<InstallWarning>* warnings) { 191 std::vector<InstallWarning>* warnings) {
159 sane_csp_parts->push_back(directive_name); 192 std::vector<std::string> sane_csp_parts(1, directive_name);
160 while (tokenizer->GetNext()) { 193 for (const base::StringPiece& directive_value : directive_values) {
161 std::string source_literal = tokenizer->token(); 194 base::StringPiece source_literal = directive_value;
Devlin 2016/12/20 17:36:38 May as well just have a mutable variable in the fo
lazyboy 2016/12/22 03:07:30 Done.
162 std::string source_lower = base::ToLowerASCII(source_literal); 195 std::string source_lower = base::ToLowerASCII(source_literal);
163 bool is_secure_csp_token = false; 196 bool is_secure_csp_token = false;
164 197
165 // We might need to relax this whitelist over time. 198 // We might need to relax this whitelist over time.
166 if (source_lower == "'self'" || source_lower == "'none'" || 199 if (source_lower == "'self'" || source_lower == "'none'" ||
167 source_lower == "http://127.0.0.1" || source_lower == "blob:" || 200 source_lower == "http://127.0.0.1" || source_lower == "blob:" ||
168 source_lower == "filesystem:" || source_lower == "http://localhost" || 201 source_lower == "filesystem:" || source_lower == "http://localhost" ||
169 base::StartsWith(source_lower, "http://127.0.0.1:", 202 base::StartsWith(source_lower, "http://127.0.0.1:",
170 base::CompareCase::SENSITIVE) || 203 base::CompareCase::SENSITIVE) ||
171 base::StartsWith(source_lower, "http://localhost:", 204 base::StartsWith(source_lower, "http://localhost:",
172 base::CompareCase::SENSITIVE) || 205 base::CompareCase::SENSITIVE) ||
173 isNonWildcardTLD(source_lower, "https://", true) || 206 isNonWildcardTLD(source_lower, "https://", true) ||
174 isNonWildcardTLD(source_lower, "chrome://", false) || 207 isNonWildcardTLD(source_lower, "chrome://", false) ||
175 isNonWildcardTLD(source_lower, 208 isNonWildcardTLD(source_lower,
176 std::string(extensions::kExtensionScheme) + 209 std::string(extensions::kExtensionScheme) +
177 url::kStandardSchemeSeparator, 210 url::kStandardSchemeSeparator,
178 false) || 211 false) ||
179 IsHashSource(source_literal) || 212 IsHashSource(source_literal) ||
180 base::StartsWith(source_lower, "chrome-extension-resource:", 213 base::StartsWith(source_lower, "chrome-extension-resource:",
181 base::CompareCase::SENSITIVE)) { 214 base::CompareCase::SENSITIVE)) {
182 is_secure_csp_token = true; 215 is_secure_csp_token = true;
183 } else if ((options & OPTIONS_ALLOW_UNSAFE_EVAL) && 216 } else if ((options & OPTIONS_ALLOW_UNSAFE_EVAL) &&
184 source_lower == "'unsafe-eval'") { 217 source_lower == "'unsafe-eval'") {
185 is_secure_csp_token = true; 218 is_secure_csp_token = true;
186 } 219 }
187 220
188 if (is_secure_csp_token) { 221 if (is_secure_csp_token) {
189 sane_csp_parts->push_back(source_literal); 222 sane_csp_parts.push_back(source_literal.as_string());
190 } else if (warnings) { 223 } else if (warnings) {
191 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage( 224 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
192 manifest_errors::kInvalidCSPInsecureValue, source_literal, 225 manifest_errors::kInvalidCSPInsecureValue, source_literal.as_string(),
193 directive_name))); 226 directive_name)));
194 } 227 }
195 } 228 }
196 // End of CSP directive that was started at the beginning of this method. If 229 // End of CSP directive that was started at the beginning of this method. If
197 // none of the values are secure, the policy will be empty and default to 230 // none of the values are secure, the policy will be empty and default to
198 // 'none', which is secure. 231 // 'none', which is secure.
199 sane_csp_parts->back().push_back(';'); 232 sane_csp_parts.back().push_back(kDirectiveSeparator);
233 return base::JoinString(sane_csp_parts, " ");
200 } 234 }
201 235
202 // Returns true if |directive_name| matches |status.directive_name|. 236 std::string GetAppSandboxSecureDirectiveValues(
Devlin 2016/12/20 17:36:38 function comments
lazyboy 2016/12/22 03:07:30 Done.
203 bool UpdateStatus(const std::string& directive_name, 237 const std::string& directive_name,
204 base::StringTokenizer* tokenizer, 238 const std::vector<base::StringPiece>& directive_values,
205 DirectiveStatus* status, 239 std::vector<InstallWarning>* warnings) {
206 int options, 240 std::vector<std::string> sane_csp_parts(1, directive_name);
207 std::vector<std::string>* sane_csp_parts, 241 bool seen_self_or_none = false;
208 std::vector<InstallWarning>* warnings) { 242 for (const base::StringPiece& directive_value : directive_values) {
209 if (directive_name != status->directive_name) 243 base::StringPiece source_literal = directive_value;
Devlin 2016/12/20 17:36:38 ditto
lazyboy 2016/12/22 03:07:29 Done.
210 return false; 244 std::string source_lower = base::ToLowerASCII(source_literal);
211 245
212 if (!status->seen_in_policy) { 246 // Keyword directive sources are surrounded with quotes, e.g. 'self',
213 status->seen_in_policy = true; 247 // 'sha256-...', 'unsafe-eval', 'nonce-...'. These do not specify a remote
214 GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts, 248 // host or '*', so keep them and restrict the rest.
215 warnings); 249 if (source_lower.size() > 1u && source_lower[0] == '\'' &&
216 } else { 250 source_lower[source_lower.size() - 1] == '\'') {
Devlin 2016/12/20 17:36:39 source_lower.back()
lazyboy 2016/12/22 03:07:30 Done.
217 // Don't show any errors for duplicate CSP directives, because it will be 251 if (source_lower == "'none'" || source_lower == "'self'")
218 // ignored by the CSP parser (http://www.w3.org/TR/CSP2/#policy-parsing). 252 seen_self_or_none |= true;
Devlin 2016/12/20 17:36:39 optional nit: could one-line this: seen_self_or_no
lazyboy 2016/12/22 03:07:30 Done.
219 GetSecureDirectiveValues(directive_name, tokenizer, options, sane_csp_parts, 253 sane_csp_parts.push_back(source_lower);
220 NULL); 254 } else if (warnings) {
255 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
256 manifest_errors::kInvalidCSPInsecureValue, source_literal.as_string(),
257 directive_name)));
258 }
221 } 259 }
222 return true; 260
261 if (!seen_self_or_none)
262 sane_csp_parts.push_back("'self'");
Devlin 2016/12/20 17:36:38 comment why we do this.
lazyboy 2016/12/22 03:07:30 Done.
263
264 sane_csp_parts.back().push_back(kDirectiveSeparator);
265 return base::JoinString(sane_csp_parts, " ");
223 } 266 }
224 267
225 // Returns true if the |plugin_type| is one of the fully sandboxed plugin types. 268 // Returns true if the |plugin_type| is one of the fully sandboxed plugin types.
226 bool PluginTypeAllowed(const std::string& plugin_type) { 269 bool PluginTypeAllowed(const std::string& plugin_type) {
227 for (size_t i = 0; i < arraysize(kSandboxedPluginTypes); ++i) { 270 for (size_t i = 0; i < arraysize(kSandboxedPluginTypes); ++i) {
228 if (plugin_type == kSandboxedPluginTypes[i]) 271 if (plugin_type == kSandboxedPluginTypes[i])
229 return true; 272 return true;
230 } 273 }
231 return false; 274 return false;
232 } 275 }
(...skipping 19 matching lines...) Expand all
252 if (!PluginTypeAllowed(tokenizer.token())) 295 if (!PluginTypeAllowed(tokenizer.token()))
253 return false; 296 return false;
254 } 297 }
255 // All listed plugin types are whitelisted. 298 // All listed plugin types are whitelisted.
256 return true; 299 return true;
257 } 300 }
258 // plugin-types not specified. 301 // plugin-types not specified.
259 return false; 302 return false;
260 } 303 }
261 304
305 using SecureDirectiveValueFunction = base::Callback<std::string(
306 const std::string& directive_name,
307 const std::vector<base::StringPiece>& directive_values,
308 std::vector<InstallWarning>* warnings)>;
309
310 class CSPDirectiveToken {
Devlin 2016/12/20 17:36:39 comments for this class + for methods
lazyboy 2016/12/22 03:07:30 Done.
311 public:
312 explicit CSPDirectiveToken(base::StringPiece directive_token)
313 : directive_token_(directive_token),
314 parsed_(false),
315 tokenizer_(directive_token.begin(), directive_token.end(), " \t\r\n") {
316 is_empty_ = !tokenizer_.GetNext();
317 if (!is_empty_)
318 directive_name_ = tokenizer_.token();
319 }
320 bool MatchAndUpdateStatus(DirectiveStatus* status,
321 const SecureDirectiveValueFunction& secure_function,
322 std::vector<InstallWarning>* warnings) {
323 if (is_empty_ || !status->Matches(directive_name_))
324 return false;
325
326 EnsureTokenPartsParsed();
327
328 bool is_duplicate_directive = status->seen_in_policy();
329 status->set_seen_in_policy();
330
331 secure_value_ = secure_function.Run(
332 directive_name_, directive_values_,
333 // Don't show any errors for duplicate CSP directives, because it will
334 // be ignored by the CSP parser
335 // (http://www.w3.org/TR/CSP2/#policy-parsing). Therefore, set warnings
336 // param to nullptr.
337 is_duplicate_directive ? nullptr : warnings);
338 return true;
339 }
340 std::string ToString() {
341 if (secure_value_)
342 return secure_value_.value();
343 // This token didn't require modification.
344 return base::StringPrintf("%s%c", directive_token_.as_string().c_str(),
Devlin 2016/12/20 17:36:39 doesn't base::StringPiece::as_string().c_str() ==
lazyboy 2016/12/22 03:07:30 .data() isn't \0 terminated. So can't use.
345 kDirectiveSeparator);
346 }
347
348 private:
349 void EnsureTokenPartsParsed() {
350 if (!parsed_) {
351 while (tokenizer_.GetNext())
352 directive_values_.push_back(tokenizer_.token_piece());
353 parsed_ = true;
354 }
355 }
356
357 base::StringPiece directive_token_;
358 std::string directive_name_;
359 std::vector<base::StringPiece> directive_values_;
360
361 base::Optional<std::string> secure_value_;
362
363 bool is_empty_;
364 bool parsed_;
365 base::CStringTokenizer tokenizer_;
366
367 DISALLOW_COPY_AND_ASSIGN(CSPDirectiveToken);
368 };
369
370 class CSPEnforcer {
Devlin 2016/12/20 17:36:38 comments
lazyboy 2016/12/22 03:07:30 Done.
371 public:
372 CSPEnforcer(bool show_missing_csp_warnings,
373 const SecureDirectiveValueFunction& secure_function)
374 : show_missing_csp_warnings_(show_missing_csp_warnings),
375 secure_function_(secure_function) {}
376 virtual ~CSPEnforcer() {}
377
378 std::string Enforce(const std::string& policy,
379 std::vector<InstallWarning>* warnings);
380
381 protected:
382 virtual std::string GetDefaultCSPValue(const DirectiveStatus& status) = 0;
383
384 std::vector<DirectiveStatus> directives_;
385 std::vector<std::string> enforced_csp_parts_;
386 const bool show_missing_csp_warnings_;
387 const SecureDirectiveValueFunction secure_function_;
388 };
Devlin 2016/12/20 17:36:38 DISALLOW_COPY_AND_ASSIGN
lazyboy 2016/12/22 03:07:29 Done.
389
390 std::string CSPEnforcer::Enforce(const std::string& policy,
391 std::vector<InstallWarning>* warnings) {
Devlin 2016/12/20 17:36:38 Maybe DCHECK(!directives_.empty())
lazyboy 2016/12/22 03:07:30 Done.
392 std::vector<std::string> enforced_csp_parts;
393
394 // If any directive that we care about isn't explicitly listed in |policy|,
395 // "default-src" fallback is used.
396 DirectiveStatus default_src_status(kDefaultSrc);
397 std::vector<InstallWarning> default_src_csp_warnings;
398
399 for (const base::StringPiece& directive_token : base::SplitStringPiece(
400 policy, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
401 CSPDirectiveToken csp_directive_token(directive_token);
402 bool matches_enforcing_directive = false;
403 for (auto& directive_status : directives_) {
404 if (csp_directive_token.MatchAndUpdateStatus(
405 &directive_status, secure_function_, warnings)) {
406 matches_enforcing_directive = true;
407 break;
408 }
409 }
410 if (!matches_enforcing_directive) {
411 csp_directive_token.MatchAndUpdateStatus(
412 &default_src_status, secure_function_, &default_src_csp_warnings);
413 }
414
415 enforced_csp_parts.push_back(csp_directive_token.ToString());
416 }
417
418 if (default_src_status.seen_in_policy()) {
419 for (const DirectiveStatus& directive_status : directives_) {
420 if (!directive_status.seen_in_policy()) {
421 // This |directive_status| falls back to "default-src". So warnings from
422 // "default-src" will apply.
423 if (warnings) {
424 warnings->insert(warnings->end(), default_src_csp_warnings.begin(),
425 default_src_csp_warnings.end());
426 }
427 break;
428 }
429 }
430 } else {
431 // Did not see "default-src".
432 // Make sure we cover all sources from |directives_|.
433 for (const DirectiveStatus& directive_status : directives_) {
434 if (directive_status.seen_in_policy()) // Already covered.
435 continue;
436 enforced_csp_parts.push_back(GetDefaultCSPValue(directive_status));
437
438 if (warnings && show_missing_csp_warnings_) {
439 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
440 manifest_errors::kInvalidCSPMissingSecureSrc,
441 directive_status.name())));
442 }
443 }
444 }
445
446 return base::JoinString(enforced_csp_parts, " ");
447 }
448
449 class ExtensionCSPEnforcer : public CSPEnforcer {
450 public:
451 ExtensionCSPEnforcer(bool allow_insecure_object_src, int options)
452 : CSPEnforcer(true, base::Bind(&GetSecureDirectiveValues, options)) {
453 directives_.push_back(DirectiveStatus(kScriptSrc));
454 if (!allow_insecure_object_src)
455 directives_.push_back(DirectiveStatus(kObjectSrc));
456 }
457
458 protected:
459 std::string GetDefaultCSPValue(const DirectiveStatus& status) override {
460 if (status.Matches(kObjectSrc))
461 return kObjectSrcDefaultDirective;
462 DCHECK(status.Matches(kScriptSrc));
463 return kScriptSrcDefaultDirective;
464 }
465
466 private:
467 DISALLOW_COPY_AND_ASSIGN(ExtensionCSPEnforcer);
468 };
469
470 class AppSandboxPageCSPEnforcer : public CSPEnforcer {
471 public:
472 AppSandboxPageCSPEnforcer()
473 : CSPEnforcer(false, base::Bind(&GetAppSandboxSecureDirectiveValues)) {
474 directives_.push_back(DirectiveStatus(kChildSrc, kFrameSrc));
475 directives_.push_back(DirectiveStatus(kScriptSrc));
476 }
477
478 protected:
479 std::string GetDefaultCSPValue(const DirectiveStatus& status) override {
480 if (status.Matches(kChildSrc))
481 return kAppSandboxSubframeSrcDefaultDirective;
482 DCHECK(status.Matches(kScriptSrc));
483 return kAppSandboxScriptSrcDefaultDirective;
484 }
485
486 private:
487 DISALLOW_COPY_AND_ASSIGN(AppSandboxPageCSPEnforcer);
488 };
489
262 } // namespace 490 } // namespace
263 491
264 bool ContentSecurityPolicyIsLegal(const std::string& policy) { 492 bool ContentSecurityPolicyIsLegal(const std::string& policy) {
265 // We block these characters to prevent HTTP header injection when 493 // We block these characters to prevent HTTP header injection when
266 // representing the content security policy as an HTTP header. 494 // representing the content security policy as an HTTP header.
267 const char kBadChars[] = {',', '\r', '\n', '\0'}; 495 const char kBadChars[] = {',', '\r', '\n', '\0'};
268 496
269 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == 497 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) ==
270 std::string::npos; 498 std::string::npos;
271 } 499 }
272 500
273 std::string SanitizeContentSecurityPolicy( 501 std::string SanitizeContentSecurityPolicy(
274 const std::string& policy, 502 const std::string& policy,
275 int options, 503 int options,
276 std::vector<InstallWarning>* warnings) { 504 std::vector<InstallWarning>* warnings) {
277 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 505 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
278 std::vector<std::string> directives = base::SplitString( 506 std::vector<std::string> directives = base::SplitString(
279 policy, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); 507 policy, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
280 508
281 DirectiveStatus default_src_status(kDefaultSrc);
282 DirectiveStatus script_src_status(kScriptSrc);
283 DirectiveStatus object_src_status(kObjectSrc);
284
285 bool allow_insecure_object_src = 509 bool allow_insecure_object_src =
286 AllowedToHaveInsecureObjectSrc(options, directives); 510 AllowedToHaveInsecureObjectSrc(options, directives);
287 511
288 std::vector<std::string> sane_csp_parts; 512 ExtensionCSPEnforcer csp_enforcer(allow_insecure_object_src, options);
289 std::vector<InstallWarning> default_src_csp_warnings; 513 return csp_enforcer.Enforce(policy, warnings);
290 for (size_t i = 0; i < directives.size(); ++i) { 514 }
291 std::string& input = directives[i];
292 base::StringTokenizer tokenizer(input, " \t\r\n");
293 if (!tokenizer.GetNext())
294 continue;
295 515
296 std::string directive_name = base::ToLowerASCII(tokenizer.token_piece()); 516 std::string GetEffectiveSandoxedPageCSP(const std::string& policy,
297 if (UpdateStatus(directive_name, &tokenizer, &default_src_status, options, 517 std::vector<InstallWarning>* warnings) {
298 &sane_csp_parts, &default_src_csp_warnings)) 518 AppSandboxPageCSPEnforcer csp_enforcer;
299 continue; 519 return csp_enforcer.Enforce(policy, warnings);
300 if (UpdateStatus(directive_name, &tokenizer, &script_src_status, options,
301 &sane_csp_parts, warnings))
302 continue;
303 if (!allow_insecure_object_src &&
304 UpdateStatus(directive_name, &tokenizer, &object_src_status, options,
305 &sane_csp_parts, warnings))
306 continue;
307
308 // Pass the other CSP directives as-is without further validation.
309 sane_csp_parts.push_back(input + ";");
310 }
311
312 if (default_src_status.seen_in_policy) {
313 if (!script_src_status.seen_in_policy ||
314 !object_src_status.seen_in_policy) {
315 // Insecure values in default-src are only relevant if either script-src
316 // or object-src is omitted.
317 if (warnings)
318 warnings->insert(warnings->end(),
319 default_src_csp_warnings.begin(),
320 default_src_csp_warnings.end());
321 }
322 } else {
323 if (!script_src_status.seen_in_policy) {
324 sane_csp_parts.push_back(kScriptSrcDefaultDirective);
325 if (warnings)
326 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
327 manifest_errors::kInvalidCSPMissingSecureSrc, kScriptSrc)));
328 }
329 if (!object_src_status.seen_in_policy && !allow_insecure_object_src) {
330 sane_csp_parts.push_back(kObjectSrcDefaultDirective);
331 if (warnings)
332 warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
333 manifest_errors::kInvalidCSPMissingSecureSrc, kObjectSrc)));
334 }
335 }
336
337 return base::JoinString(sane_csp_parts, " ");
338 } 520 }
339 521
340 bool ContentSecurityPolicyIsSandboxed( 522 bool ContentSecurityPolicyIsSandboxed(
341 const std::string& policy, Manifest::Type type) { 523 const std::string& policy, Manifest::Type type) {
342 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 524 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
343 bool seen_sandbox = false; 525 bool seen_sandbox = false;
344 for (const std::string& input : base::SplitString( 526 for (const std::string& input : base::SplitString(
345 policy, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { 527 policy, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
346 base::StringTokenizer tokenizer(input, " \t\r\n"); 528 base::StringTokenizer tokenizer(input, " \t\r\n");
347 if (!tokenizer.GetNext()) 529 if (!tokenizer.GetNext())
(...skipping 19 matching lines...) Expand all
367 } 549 }
368 } 550 }
369 } 551 }
370 552
371 return seen_sandbox; 553 return seen_sandbox;
372 } 554 }
373 555
374 } // namespace csp_validator 556 } // namespace csp_validator
375 557
376 } // namespace extensions 558 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698