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 15 namespace extensions { 16 17 namespace csp_validator { 18 19 namespace { 20 21 const char kDefaultSrc[] = "default-src"; 22 const char kScriptSrc[] = "script-src"; 23 const char kObjectSrc[] = "object-src"; 24 25 const char kSandboxDirectiveName[] = "sandbox"; 26 const char kAllowSameOriginToken[] = "allow-same-origin"; 27 const char kAllowTopNavigation[] = "allow-top-navigation"; 28 29 struct DirectiveStatus { 30 explicit DirectiveStatus(const char* name) 31 : directive_name(name) 32 , seen_in_policy(false) 33 , is_secure(false) { 34 } 35 36 const char* directive_name; 37 bool seen_in_policy; 38 bool is_secure; 39 }; 40 41 bool HasOnlySecureTokens(base::StringTokenizer& tokenizer, 42 Manifest::Type type) { 43 while (tokenizer.GetNext()) { 44 std::string source = tokenizer.token(); 45 StringToLowerASCII(&source); 46 47 // Don't alow whitelisting of all hosts. This boils down to: 48 // 1. Maximum of 2 '*' characters. 49 // 2. Each '*' is either followed by a '.' or preceded by a ':' 50 int wildcards = 0; 51 size_t length = source.length(); 52 for (size_t i = 0; i < length; ++i) { 53 if (source[i] == L'*') { 54 wildcards++; 55 if (wildcards > 2) 56 return false; 57 58 bool isWildcardPort = i > 0 && source[i - 1] == L':'; 59 bool isWildcardSubdomain = i + 1 < length && source[i + 1] == L'.'; 60 if (!isWildcardPort && !isWildcardSubdomain) 61 return false; 62 } 63 } 64 65 // We might need to relax this whitelist over time. 66 if (source == "'self'" || 67 source == "'none'" || 68 source == "http://127.0.0.1" || 69 LowerCaseEqualsASCII(source, "blob:") || 70 LowerCaseEqualsASCII(source, "filesystem:") || 71 LowerCaseEqualsASCII(source, "http://localhost") || 72 StartsWithASCII(source, "http://127.0.0.1:", false) || 73 StartsWithASCII(source, "http://localhost:", false) || 74 StartsWithASCII(source, "https://", true) || 75 StartsWithASCII(source, "chrome://", true) || 76 StartsWithASCII(source, 77 std::string(extensions::kExtensionScheme) + 78 url::kStandardSchemeSeparator, 79 true) || 80 StartsWithASCII(source, "chrome-extension-resource:", true)) { 81 continue; 82 } 83 84 // crbug.com/146487 85 if (type == Manifest::TYPE_EXTENSION || 86 type == Manifest::TYPE_LEGACY_PACKAGED_APP) { 87 if (source == "'unsafe-eval'") 88 continue; 89 } 90 91 return false; 92 } 93 94 return true; // Empty values default to 'none', which is secure. 95 } 96 97 // Returns true if |directive_name| matches |status.directive_name|. 98 bool UpdateStatus(const std::string& directive_name, 99 base::StringTokenizer& tokenizer, 100 DirectiveStatus* status, 101 Manifest::Type type) { 102 if (status->seen_in_policy) 103 return false; 104 if (directive_name != status->directive_name) 105 return false; 106 status->seen_in_policy = true; 107 status->is_secure = HasOnlySecureTokens(tokenizer, type); 108 return true; 109 } 110 111 } // namespace 112 113 bool ContentSecurityPolicyIsLegal(const std::string& policy) { 114 // We block these characters to prevent HTTP header injection when 115 // representing the content security policy as an HTTP header. 116 const char kBadChars[] = {',', '\r', '\n', '\0'}; 117 118 return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) == 119 std::string::npos; 120 } 121 122 bool ContentSecurityPolicyIsSecure(const std::string& policy, 123 Manifest::Type type) { 124 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 125 std::vector<std::string> directives; 126 base::SplitString(policy, ';', &directives); 127 128 DirectiveStatus default_src_status(kDefaultSrc); 129 DirectiveStatus script_src_status(kScriptSrc); 130 DirectiveStatus object_src_status(kObjectSrc); 131 132 for (size_t i = 0; i < directives.size(); ++i) { 133 std::string& input = directives[i]; 134 base::StringTokenizer tokenizer(input, " \t\r\n"); 135 if (!tokenizer.GetNext()) 136 continue; 137 138 std::string directive_name = tokenizer.token(); 139 StringToLowerASCII(&directive_name); 140 141 if (UpdateStatus(directive_name, tokenizer, &default_src_status, type)) 142 continue; 143 if (UpdateStatus(directive_name, tokenizer, &script_src_status, type)) 144 continue; 145 if (UpdateStatus(directive_name, tokenizer, &object_src_status, type)) 146 continue; 147 } 148 149 if (script_src_status.seen_in_policy && !script_src_status.is_secure) 150 return false; 151 152 if (object_src_status.seen_in_policy && !object_src_status.is_secure) 153 return false; 154 155 if (default_src_status.seen_in_policy && !default_src_status.is_secure) { 156 return script_src_status.seen_in_policy && 157 object_src_status.seen_in_policy; 158 } 159 160 return default_src_status.seen_in_policy || 161 (script_src_status.seen_in_policy && object_src_status.seen_in_policy); 162 } 163 164 bool ContentSecurityPolicyIsSandboxed( 165 const std::string& policy, Manifest::Type type) { 166 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm. 167 std::vector<std::string> directives; 168 base::SplitString(policy, ';', &directives); 169 170 bool seen_sandbox = false; 171 172 for (size_t i = 0; i < directives.size(); ++i) { 173 std::string& input = directives[i]; 174 base::StringTokenizer tokenizer(input, " \t\r\n"); 175 if (!tokenizer.GetNext()) 176 continue; 177 178 std::string directive_name = tokenizer.token(); 179 StringToLowerASCII(&directive_name); 180 181 if (directive_name != kSandboxDirectiveName) 182 continue; 183 184 seen_sandbox = true; 185 186 while (tokenizer.GetNext()) { 187 std::string token = tokenizer.token(); 188 StringToLowerASCII(&token); 189 190 // The same origin token negates the sandboxing. 191 if (token == kAllowSameOriginToken) 192 return false; 193 194 // Platform apps don't allow navigation. 195 if (type == Manifest::TYPE_PLATFORM_APP) { 196 if (token == kAllowTopNavigation) 197 return false; 198 } 199 } 200 } 201 202 return seen_sandbox; 203 } 204 205 } // namespace csp_validator 206 207 } // namespace extensions 208