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