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

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

Issue 760513003: Only allow insecure object-src directives for whitelisted mime types (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@extensions-csp3
Patch Set: Created 6 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 <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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698