csp_validator.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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 16namespace extensions { 17 18namespace csp_validator { 19 20namespace { 21 22const char kDefaultSrc[] = "default-src"; 23const char kScriptSrc[] = "script-src"; 24const char kObjectSrc[] = "object-src"; 25 26const char kSandboxDirectiveName[] = "sandbox"; 27const char kAllowSameOriginToken[] = "allow-same-origin"; 28const char kAllowTopNavigation[] = "allow-top-navigation"; 29 30struct 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". 45bool 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 // Wildcards on subdomains of a TLD are not allowed. 91 size_t registry_length = net::registry_controlled_domains::GetRegistryLength( 92 host, 93 net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, 94 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); 95 return registry_length != 0; 96} 97 98bool HasOnlySecureTokens(base::StringTokenizer& tokenizer, 99 Manifest::Type type) { 100 while (tokenizer.GetNext()) { 101 std::string source = tokenizer.token(); 102 base::StringToLowerASCII(&source); 103 104 // We might need to relax this whitelist over time. 105 if (source == "'self'" || 106 source == "'none'" || 107 source == "http://127.0.0.1" || 108 LowerCaseEqualsASCII(source, "blob:") || 109 LowerCaseEqualsASCII(source, "filesystem:") || 110 LowerCaseEqualsASCII(source, "http://localhost") || 111 StartsWithASCII(source, "http://127.0.0.1:", true) || 112 StartsWithASCII(source, "http://localhost:", true) || 113 isNonWildcardTLD(source, "https://", true) || 114 isNonWildcardTLD(source, "chrome://", false) || 115 isNonWildcardTLD(source, 116 std::string(extensions::kExtensionScheme) + 117 url::kStandardSchemeSeparator, 118 false) || 119 StartsWithASCII(source, "chrome-extension-resource:", true)) { 120 continue; 121 } 122 123 // crbug.com/146487 124 if (type == Manifest::TYPE_EXTENSION || 125 type == Manifest::TYPE_LEGACY_PACKAGED_APP) { 126 if (source == "'unsafe-eval'") 127 continue; 128 } 129 130 return false; 131 } 132 133 return true; // Empty values default to 'none', which is secure. 134} 135 136// Returns true if |directive_name| matches |status.directive_name|. 137bool UpdateStatus(const std::string& directive_name, 138 base::StringTokenizer& tokenizer, 139 DirectiveStatus* status, 140 Manifest::Type type) { 141 if (status->seen_in_policy) 142 return false; 143 if (directive_name != status->directive_name) 144 return false; 145 status->seen_in_policy = true; 146 status->is_secure = HasOnlySecureTokens(tokenizer, type); 147 return true; 148} 149 150} // namespace 151 152bool ContentSecurityPolicyIsLegal(const std::string& policy) { 153 // We block these characters to prevent HTTP header injection when 154 // representing the content security policy as an HTTP header. 155 const char kBadChars[] = {',', '\r', '\n', '\0'}; 156 157 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == 158 std::string::npos; 159} 160 161bool ContentSecurityPolicyIsSecure(const std::string& policy, 162 Manifest::Type type) { 163 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 164 std::vector<std::string> directives; 165 base::SplitString(policy, ';', &directives); 166 167 DirectiveStatus default_src_status(kDefaultSrc); 168 DirectiveStatus script_src_status(kScriptSrc); 169 DirectiveStatus object_src_status(kObjectSrc); 170 171 for (size_t i = 0; i < directives.size(); ++i) { 172 std::string& input = directives[i]; 173 base::StringTokenizer tokenizer(input, " \t\r\n"); 174 if (!tokenizer.GetNext()) 175 continue; 176 177 std::string directive_name = tokenizer.token(); 178 base::StringToLowerASCII(&directive_name); 179 180 if (UpdateStatus(directive_name, tokenizer, &default_src_status, type)) 181 continue; 182 if (UpdateStatus(directive_name, tokenizer, &script_src_status, type)) 183 continue; 184 if (UpdateStatus(directive_name, tokenizer, &object_src_status, type)) 185 continue; 186 } 187 188 if (script_src_status.seen_in_policy && !script_src_status.is_secure) 189 return false; 190 191 if (object_src_status.seen_in_policy && !object_src_status.is_secure) 192 return false; 193 194 if (default_src_status.seen_in_policy && !default_src_status.is_secure) { 195 return script_src_status.seen_in_policy && 196 object_src_status.seen_in_policy; 197 } 198 199 return default_src_status.seen_in_policy || 200 (script_src_status.seen_in_policy && object_src_status.seen_in_policy); 201} 202 203bool ContentSecurityPolicyIsSandboxed( 204 const std::string& policy, Manifest::Type type) { 205 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 206 std::vector<std::string> directives; 207 base::SplitString(policy, ';', &directives); 208 209 bool seen_sandbox = false; 210 211 for (size_t i = 0; i < directives.size(); ++i) { 212 std::string& input = directives[i]; 213 base::StringTokenizer tokenizer(input, " \t\r\n"); 214 if (!tokenizer.GetNext()) 215 continue; 216 217 std::string directive_name = tokenizer.token(); 218 base::StringToLowerASCII(&directive_name); 219 220 if (directive_name != kSandboxDirectiveName) 221 continue; 222 223 seen_sandbox = true; 224 225 while (tokenizer.GetNext()) { 226 std::string token = tokenizer.token(); 227 base::StringToLowerASCII(&token); 228 229 // The same origin token negates the sandboxing. 230 if (token == kAllowSameOriginToken) 231 return false; 232 233 // Platform apps don't allow navigation. 234 if (type == Manifest::TYPE_PLATFORM_APP) { 235 if (token == kAllowTopNavigation) 236 return false; 237 } 238 } 239 } 240 241 return seen_sandbox; 242} 243 244} // namespace csp_validator 245 246} // namespace extensions 247