csp_validator.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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
15namespace extensions {
16
17namespace csp_validator {
18
19namespace {
20
21const char kDefaultSrc[] = "default-src";
22const char kScriptSrc[] = "script-src";
23const char kObjectSrc[] = "object-src";
24
25const char kSandboxDirectiveName[] = "sandbox";
26const char kAllowSameOriginToken[] = "allow-same-origin";
27const char kAllowTopNavigation[] = "allow-top-navigation";
28
29struct 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
41bool HasOnlySecureTokens(base::StringTokenizer& tokenizer,
42                         Manifest::Type type) {
43  while (tokenizer.GetNext()) {
44    std::string source = tokenizer.token();
45    base::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|.
98bool 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
113bool 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
122bool 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    base::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
164bool 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    base::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      base::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