OLD | NEW |
| (Empty) |
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 | |
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 | |
OLD | NEW |