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 3 // found in the LICENSE file. 4 5 #include "extensions/common/csp_validator.h" 6 7 #include <vector> 8 9 #include "base/strings/string_split.h" 10 #include "base/strings/string_tokenizer.h" 11 #include "base/strings/string_util.h" 12 #include "content/public/common/url_constants.h" 13 #include "extensions/common/constants.h" 14 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 15 16 namespace extensions { 17 18 namespace csp_validator { 19 20 namespace { 21 22 const char kDefaultSrc[] = "default-src"; 23 const char kScriptSrc[] = "script-src"; 24 const char kObjectSrc[] = "object-src"; 25 26 const char kSandboxDirectiveName[] = "sandbox"; 27 const char kAllowSameOriginToken[] = "allow-same-origin"; 28 const char kAllowTopNavigation[] = "allow-top-navigation"; 29 30 struct DirectiveStatus { 31 explicit DirectiveStatus(const char* name) 32 : directive_name(name) 33 , seen_in_policy(false) 34 , is_secure(false) { 35 } 36 37 const char* directive_name; 38 bool seen_in_policy; 39 bool is_secure; 40 }; 41 42 // Returns whether |url| starts with |scheme_and_separator| and does not have a 43 // too permissive wildcard host name. If |should_check_rcd| is true, then the 44 // Public suffix list is used to exclude wildcard TLDs such as "https://*.org". 45 bool isNonWildcardTLD(const std::string& url, 46 const std::string& scheme_and_separator, 47 bool should_check_rcd) { 48 if (!StartsWithASCII(url, scheme_and_separator, true)) 49 return false; 50 51 size_t start_of_host = scheme_and_separator.length(); 52 53 size_t end_of_host = url.find("/", start_of_host); 54 if (end_of_host == std::string::npos) 55 end_of_host = url.size(); 56 57 // Note: It is sufficient to only compare the first character against '*' 58 // because the CSP only allows wildcards at the start of a directive, see 59 // host-source and host-part at http://www.w3.org/TR/CSP2/#source-list-syntax 60 bool is_wildcard_subdomain = end_of_host > start_of_host + 2 && 61 url[start_of_host] == '*' && url[start_of_host + 1] == '.'; 62 if (is_wildcard_subdomain) 63 start_of_host += 2; 64 65 size_t start_of_port = url.rfind(":", end_of_host); 66 // The ":" check at the end of the following condition is used to avoid 67 // treating the last part of an IPv6 address as a port. 68 if (start_of_port > start_of_host && url[start_of_port - 1] != ':') { 69 bool is_valid_port = false; 70 // Do a quick sanity check. The following check could mistakenly flag 71 // ":123456" or ":****" as valid, but that does not matter because the 72 // relaxing CSP directive will just be ignored by Blink. 73 for (size_t i = start_of_port + 1; i < end_of_host; ++i) { 74 is_valid_port = IsAsciiDigit(url[i]) || url[i] == '*'; 75 if (!is_valid_port) 76 break; 77 } 78 if (is_valid_port) 79 end_of_host = start_of_port; 80 } 81 82 std::string host(url, start_of_host, end_of_host - start_of_host); 83 // Global wildcards are not allowed. 84 if (host.empty() || host.find("*") != std::string::npos) 85 return false; 86 87 if (!is_wildcard_subdomain || !should_check_rcd) 88 return true; 89 90 // Allow *.googleapis.com to be whitelisted for backwards-compatibility. 91 // (crbug.com/409952) 92 if (host == "googleapis.com") 93 return true; 94 95 // Wildcards on subdomains of a TLD are not allowed. 96 size_t registry_length = net::registry_controlled_domains::GetRegistryLength( 97 host, 98 net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, 99 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); 100 return registry_length != 0; 101 } 102 103 bool HasOnlySecureTokens(base::StringTokenizer& tokenizer, 104 Manifest::Type type) { 105 while (tokenizer.GetNext()) { 106 std::string source = tokenizer.token(); 107 base::StringToLowerASCII(&source); 108 109 // We might need to relax this whitelist over time. 110 if (source == "'self'" || 111 source == "'none'" || 112 source == "http://127.0.0.1" || 113 LowerCaseEqualsASCII(source, "blob:") || 114 LowerCaseEqualsASCII(source, "filesystem:") || 115 LowerCaseEqualsASCII(source, "http://localhost") || 116 StartsWithASCII(source, "http://127.0.0.1:", true) || 117 StartsWithASCII(source, "http://localhost:", true) || 118 isNonWildcardTLD(source, "https://", true) || 119 isNonWildcardTLD(source, "chrome://", false) || 120 isNonWildcardTLD(source, 121 std::string(extensions::kExtensionScheme) + 122 url::kStandardSchemeSeparator, 123 false) || 124 StartsWithASCII(source, "chrome-extension-resource:", true)) { 125 continue; 126 } 127 128 // crbug.com/146487 129 if (type == Manifest::TYPE_EXTENSION || 130 type == Manifest::TYPE_LEGACY_PACKAGED_APP) { 131 if (source == "'unsafe-eval'") 132 continue; 133 } 134 135 return false; 136 } 137 138 return true; // Empty values default to 'none', which is secure. 139 } 140 141 // Returns true if |directive_name| matches |status.directive_name|. 142 bool UpdateStatus(const std::string& directive_name, 143 base::StringTokenizer& tokenizer, 144 DirectiveStatus* status, 145 Manifest::Type type) { 146 if (status->seen_in_policy) 147 return false; 148 if (directive_name != status->directive_name) 149 return false; 150 status->seen_in_policy = true; 151 status->is_secure = HasOnlySecureTokens(tokenizer, type); 152 return true; 153 } 154 155 } // namespace 156 157 bool ContentSecurityPolicyIsLegal(const std::string& policy) { 158 // We block these characters to prevent HTTP header injection when 159 // representing the content security policy as an HTTP header. 160 const char kBadChars[] = {',', '\r', '\n', '\0'}; 161 162 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == 163 std::string::npos; 164 } 165 166 bool ContentSecurityPolicyIsSecure(const std::string& policy, 167 Manifest::Type type) { 168 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 169 std::vector<std::string> directives; 170 base::SplitString(policy, ';', &directives); 171 172 DirectiveStatus default_src_status(kDefaultSrc); 173 DirectiveStatus script_src_status(kScriptSrc); 174 DirectiveStatus object_src_status(kObjectSrc); 175 176 for (size_t i = 0; i < directives.size(); ++i) { 177 std::string& input = directives[i]; 178 base::StringTokenizer tokenizer(input, " \t\r\n"); 179 if (!tokenizer.GetNext()) 180 continue; 181 182 std::string directive_name = tokenizer.token(); 183 base::StringToLowerASCII(&directive_name); 184 185 if (UpdateStatus(directive_name, tokenizer, &default_src_status, type)) 186 continue; 187 if (UpdateStatus(directive_name, tokenizer, &script_src_status, type)) 188 continue; 189 if (UpdateStatus(directive_name, tokenizer, &object_src_status, type)) 190 continue; 191 } 192 193 if (script_src_status.seen_in_policy && !script_src_status.is_secure) 194 return false; 195 196 if (object_src_status.seen_in_policy && !object_src_status.is_secure) 197 return false; 198 199 if (default_src_status.seen_in_policy && !default_src_status.is_secure) { 200 return script_src_status.seen_in_policy && 201 object_src_status.seen_in_policy; 202 } 203 204 return default_src_status.seen_in_policy || 205 (script_src_status.seen_in_policy && object_src_status.seen_in_policy); 206 } 207 208 bool ContentSecurityPolicyIsSandboxed( 209 const std::string& policy, Manifest::Type type) { 210 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 211 std::vector<std::string> directives; 212 base::SplitString(policy, ';', &directives); 213 214 bool seen_sandbox = false; 215 216 for (size_t i = 0; i < directives.size(); ++i) { 217 std::string& input = directives[i]; 218 base::StringTokenizer tokenizer(input, " \t\r\n"); 219 if (!tokenizer.GetNext()) 220 continue; 221 222 std::string directive_name = tokenizer.token(); 223 base::StringToLowerASCII(&directive_name); 224 225 if (directive_name != kSandboxDirectiveName) 226 continue; 227 228 seen_sandbox = true; 229 230 while (tokenizer.GetNext()) { 231 std::string token = tokenizer.token(); 232 base::StringToLowerASCII(&token); 233 234 // The same origin token negates the sandboxing. 235 if (token == kAllowSameOriginToken) 236 return false; 237 238 // Platform apps don't allow navigation. 239 if (type == Manifest::TYPE_PLATFORM_APP) { 240 if (token == kAllowTopNavigation) 241 return false; 242 } 243 } 244 } 245 246 return seen_sandbox; 247 } 248 249 } // namespace csp_validator 250 251 } // namespace extensions 252