• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
DirectiveStatusextensions::csp_validator::__anon727c5fc50111::DirectiveStatus30   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 
HasOnlySecureTokens(base::StringTokenizer & tokenizer,Manifest::Type type)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|.
UpdateStatus(const std::string & directive_name,base::StringTokenizer & tokenizer,DirectiveStatus * status,Manifest::Type type)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 
ContentSecurityPolicyIsLegal(const std::string & policy)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 
ContentSecurityPolicyIsSecure(const std::string & policy,Manifest::Type type)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 
ContentSecurityPolicyIsSandboxed(const std::string & policy,Manifest::Type type)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