OLD | NEW |
---|---|
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 "net/base/registry_controlled_domains/registry_controlled_domain.h" | 14 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
15 | 15 |
16 namespace extensions { | 16 namespace extensions { |
17 | 17 |
18 namespace csp_validator { | 18 namespace csp_validator { |
19 | 19 |
20 namespace { | 20 namespace { |
21 | 21 |
22 const char kDefaultSrc[] = "default-src"; | 22 const char kDefaultSrc[] = "default-src"; |
23 const char kScriptSrc[] = "script-src"; | 23 const char kScriptSrc[] = "script-src"; |
24 const char kObjectSrc[] = "object-src"; | 24 const char kObjectSrc[] = "object-src"; |
25 const char kPluginTypes[] = "plugin-types"; | |
25 | 26 |
26 const char kSandboxDirectiveName[] = "sandbox"; | 27 const char kSandboxDirectiveName[] = "sandbox"; |
27 const char kAllowSameOriginToken[] = "allow-same-origin"; | 28 const char kAllowSameOriginToken[] = "allow-same-origin"; |
28 const char kAllowTopNavigation[] = "allow-top-navigation"; | 29 const char kAllowTopNavigation[] = "allow-top-navigation"; |
29 | 30 |
31 // 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. | |
33 const char* const kSandboxedPluginTypes[] = { | |
34 "application/pdf", | |
35 "application/x-pnacl" | |
36 }; | |
37 | |
30 struct DirectiveStatus { | 38 struct DirectiveStatus { |
31 explicit DirectiveStatus(const char* name) | 39 explicit DirectiveStatus(const char* name) |
32 : directive_name(name) | 40 : directive_name(name) |
33 , seen_in_policy(false) | 41 , seen_in_policy(false) |
34 , is_secure(false) { | 42 , is_secure(false) { |
35 } | 43 } |
36 | 44 |
37 const char* directive_name; | 45 const char* directive_name; |
38 bool seen_in_policy; | 46 bool seen_in_policy; |
39 bool is_secure; | 47 bool is_secure; |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
100 | 108 |
101 // Wildcards on subdomains of a TLD are not allowed. | 109 // Wildcards on subdomains of a TLD are not allowed. |
102 size_t registry_length = net::registry_controlled_domains::GetRegistryLength( | 110 size_t registry_length = net::registry_controlled_domains::GetRegistryLength( |
103 host, | 111 host, |
104 net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, | 112 net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, |
105 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); | 113 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
106 return registry_length != 0; | 114 return registry_length != 0; |
107 } | 115 } |
108 | 116 |
109 bool HasOnlySecureTokens(base::StringTokenizer& tokenizer, | 117 bool HasOnlySecureTokens(base::StringTokenizer& tokenizer, |
110 Manifest::Type type) { | 118 int options) { |
111 while (tokenizer.GetNext()) { | 119 while (tokenizer.GetNext()) { |
112 std::string source = tokenizer.token(); | 120 std::string source = tokenizer.token(); |
113 base::StringToLowerASCII(&source); | 121 base::StringToLowerASCII(&source); |
114 | 122 |
115 // We might need to relax this whitelist over time. | 123 // We might need to relax this whitelist over time. |
116 if (source == "'self'" || | 124 if (source == "'self'" || |
117 source == "'none'" || | 125 source == "'none'" || |
118 source == "http://127.0.0.1" || | 126 source == "http://127.0.0.1" || |
119 LowerCaseEqualsASCII(source, "blob:") || | 127 LowerCaseEqualsASCII(source, "blob:") || |
120 LowerCaseEqualsASCII(source, "filesystem:") || | 128 LowerCaseEqualsASCII(source, "filesystem:") || |
121 LowerCaseEqualsASCII(source, "http://localhost") || | 129 LowerCaseEqualsASCII(source, "http://localhost") || |
122 StartsWithASCII(source, "http://127.0.0.1:", true) || | 130 StartsWithASCII(source, "http://127.0.0.1:", true) || |
123 StartsWithASCII(source, "http://localhost:", true) || | 131 StartsWithASCII(source, "http://localhost:", true) || |
124 isNonWildcardTLD(source, "https://", true) || | 132 isNonWildcardTLD(source, "https://", true) || |
125 isNonWildcardTLD(source, "chrome://", false) || | 133 isNonWildcardTLD(source, "chrome://", false) || |
126 isNonWildcardTLD(source, | 134 isNonWildcardTLD(source, |
127 std::string(extensions::kExtensionScheme) + | 135 std::string(extensions::kExtensionScheme) + |
128 url::kStandardSchemeSeparator, | 136 url::kStandardSchemeSeparator, |
129 false) || | 137 false) || |
130 StartsWithASCII(source, "chrome-extension-resource:", true)) { | 138 StartsWithASCII(source, "chrome-extension-resource:", true)) { |
131 continue; | 139 continue; |
132 } | 140 } |
133 | 141 |
134 // crbug.com/146487 | 142 if (options & OPTIONS_ALLOW_UNSAFE_EVAL) { |
135 if (type == Manifest::TYPE_EXTENSION || | |
136 type == Manifest::TYPE_LEGACY_PACKAGED_APP) { | |
137 if (source == "'unsafe-eval'") | 143 if (source == "'unsafe-eval'") |
138 continue; | 144 continue; |
139 } | 145 } |
140 | 146 |
141 return false; | 147 return false; |
142 } | 148 } |
143 | 149 |
144 return true; // Empty values default to 'none', which is secure. | 150 return true; // Empty values default to 'none', which is secure. |
145 } | 151 } |
146 | 152 |
147 // Returns true if |directive_name| matches |status.directive_name|. | 153 // Returns true if |directive_name| matches |status.directive_name|. |
148 bool UpdateStatus(const std::string& directive_name, | 154 bool UpdateStatus(const std::string& directive_name, |
149 base::StringTokenizer& tokenizer, | 155 base::StringTokenizer& tokenizer, |
150 DirectiveStatus* status, | 156 DirectiveStatus* status, |
151 Manifest::Type type) { | 157 int options) { |
152 if (status->seen_in_policy) | 158 if (status->seen_in_policy) |
153 return false; | 159 return false; |
154 if (directive_name != status->directive_name) | 160 if (directive_name != status->directive_name) |
155 return false; | 161 return false; |
156 status->seen_in_policy = true; | 162 status->seen_in_policy = true; |
157 status->is_secure = HasOnlySecureTokens(tokenizer, type); | 163 status->is_secure = HasOnlySecureTokens(tokenizer, options); |
158 return true; | 164 return true; |
159 } | 165 } |
160 | 166 |
167 // Parses the plugin-types directive and returns the list of mime types | |
168 // specified in |plugin_types|. | |
169 bool ParsePluginTypes(const std::string& directive_name, | |
170 base::StringTokenizer& tokenizer, | |
171 std::vector<std::string>* plugin_types) { | |
172 DCHECK(plugin_types); | |
173 | |
174 if (directive_name != kPluginTypes || !plugin_types->empty()) | |
175 return false; | |
176 | |
177 while (tokenizer.GetNext()) { | |
178 std::string mime_type = tokenizer.token(); | |
179 base::StringToLowerASCII(&mime_type); | |
180 // Since we're comparing the mime types to a whitelist, we don't check them | |
181 // for strict validity right now. | |
182 plugin_types->push_back(mime_type); | |
183 } | |
184 | |
185 return true; | |
186 } | |
187 | |
188 // Returns true if the |plugin_type| is one of the fully sandboxed plugin types. | |
189 bool PluginTypeAllowed(const std::string& plugin_type) { | |
190 for (size_t i = 0; i < arraysize(kSandboxedPluginTypes); ++i) { | |
191 if (plugin_type == kSandboxedPluginTypes[i]) | |
192 return true; | |
193 } | |
194 return false; | |
195 } | |
196 | |
197 // Returns true if the policy is allowed to contain an insecure object-src | |
198 // directive. This requires OPTIONS_ALLOW_INSECURE_OBJECT_SRC to be specified | |
199 // as an option and the plugin-types that can be loaded must be restricted to | |
200 // the set specified in kSandboxedPluginTypes. | |
201 bool AllowedToHaveInsecureObjectSrc( | |
202 int options, | |
203 const std::vector<std::string>& plugin_types) { | |
204 if (!(options & OPTIONS_ALLOW_INSECURE_OBJECT_SRC)) | |
205 return false; | |
206 | |
207 // plugin-types must be specified. | |
208 if (plugin_types.empty()) | |
209 return false; | |
210 | |
211 bool only_sandboxed_plugins_specified = true; | |
212 for (size_t i = 0; i < plugin_types.size(); ++i) { | |
213 if (!PluginTypeAllowed(plugin_types[i])) { | |
214 only_sandboxed_plugins_specified = false; | |
Mike West
2014/11/25 13:42:36
This is probably equally clear if you just return
raymes
2014/11/25 13:51:05
Done.
| |
215 break; | |
216 } | |
217 } | |
218 | |
219 return only_sandboxed_plugins_specified; | |
220 } | |
221 | |
161 } // namespace | 222 } // namespace |
162 | 223 |
163 bool ContentSecurityPolicyIsLegal(const std::string& policy) { | 224 bool ContentSecurityPolicyIsLegal(const std::string& policy) { |
164 // We block these characters to prevent HTTP header injection when | 225 // We block these characters to prevent HTTP header injection when |
165 // representing the content security policy as an HTTP header. | 226 // representing the content security policy as an HTTP header. |
166 const char kBadChars[] = {',', '\r', '\n', '\0'}; | 227 const char kBadChars[] = {',', '\r', '\n', '\0'}; |
167 | 228 |
168 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == | 229 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == |
169 std::string::npos; | 230 std::string::npos; |
170 } | 231 } |
171 | 232 |
172 bool ContentSecurityPolicyIsSecure(const std::string& policy, | 233 bool ContentSecurityPolicyIsSecure(const std::string& policy, |
173 Manifest::Type type) { | 234 int options) { |
174 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. | 235 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. |
175 std::vector<std::string> directives; | 236 std::vector<std::string> directives; |
176 base::SplitString(policy, ';', &directives); | 237 base::SplitString(policy, ';', &directives); |
177 | 238 |
178 DirectiveStatus default_src_status(kDefaultSrc); | 239 DirectiveStatus default_src_status(kDefaultSrc); |
179 DirectiveStatus script_src_status(kScriptSrc); | 240 DirectiveStatus script_src_status(kScriptSrc); |
180 DirectiveStatus object_src_status(kObjectSrc); | 241 DirectiveStatus object_src_status(kObjectSrc); |
181 | 242 |
243 std::vector<std::string> plugin_types; | |
244 | |
182 for (size_t i = 0; i < directives.size(); ++i) { | 245 for (size_t i = 0; i < directives.size(); ++i) { |
183 std::string& input = directives[i]; | 246 std::string& input = directives[i]; |
184 base::StringTokenizer tokenizer(input, " \t\r\n"); | 247 base::StringTokenizer tokenizer(input, " \t\r\n"); |
185 if (!tokenizer.GetNext()) | 248 if (!tokenizer.GetNext()) |
186 continue; | 249 continue; |
187 | 250 |
188 std::string directive_name = tokenizer.token(); | 251 std::string directive_name = tokenizer.token(); |
189 base::StringToLowerASCII(&directive_name); | 252 base::StringToLowerASCII(&directive_name); |
190 | 253 |
191 if (UpdateStatus(directive_name, tokenizer, &default_src_status, type)) | 254 if (UpdateStatus(directive_name, tokenizer, &default_src_status, options)) |
192 continue; | 255 continue; |
193 if (UpdateStatus(directive_name, tokenizer, &script_src_status, type)) | 256 if (UpdateStatus(directive_name, tokenizer, &script_src_status, options)) |
194 continue; | 257 continue; |
195 if (UpdateStatus(directive_name, tokenizer, &object_src_status, type)) | 258 if (UpdateStatus(directive_name, tokenizer, &object_src_status, options)) |
259 continue; | |
260 if (ParsePluginTypes(directive_name, tokenizer, &plugin_types)) | |
196 continue; | 261 continue; |
197 } | 262 } |
198 | 263 |
199 if (script_src_status.seen_in_policy && !script_src_status.is_secure) | 264 if (script_src_status.seen_in_policy && !script_src_status.is_secure) |
200 return false; | 265 return false; |
201 | 266 |
202 if (object_src_status.seen_in_policy && !object_src_status.is_secure) | 267 if (object_src_status.seen_in_policy && !object_src_status.is_secure) { |
203 return false; | 268 // Note that this does not fully check the object-src source list for |
269 // validity but Blink will do this anyway. | |
270 if (!AllowedToHaveInsecureObjectSrc(options, plugin_types)) | |
271 return false; | |
272 } | |
204 | 273 |
205 if (default_src_status.seen_in_policy && !default_src_status.is_secure) { | 274 if (default_src_status.seen_in_policy && !default_src_status.is_secure) { |
206 return script_src_status.seen_in_policy && | 275 return script_src_status.seen_in_policy && |
207 object_src_status.seen_in_policy; | 276 object_src_status.seen_in_policy; |
208 } | 277 } |
209 | 278 |
210 return default_src_status.seen_in_policy || | 279 return default_src_status.seen_in_policy || |
211 (script_src_status.seen_in_policy && object_src_status.seen_in_policy); | 280 (script_src_status.seen_in_policy && object_src_status.seen_in_policy); |
212 } | 281 } |
213 | 282 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
248 } | 317 } |
249 } | 318 } |
250 } | 319 } |
251 | 320 |
252 return seen_sandbox; | 321 return seen_sandbox; |
253 } | 322 } |
254 | 323 |
255 } // namespace csp_validator | 324 } // namespace csp_validator |
256 | 325 |
257 } // namespace extensions | 326 } // namespace extensions |
OLD | NEW |