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