csp_validator.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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
11namespace extensions {
12
13namespace csp_validator {
14
15namespace {
16
17const char kDefaultSrc[] = "default-src";
18const char kScriptSrc[] = "script-src";
19const char kObjectSrc[] = "object-src";
20
21const char kSandboxDirectiveName[] = "sandbox";
22const char kAllowSameOriginToken[] = "allow-same-origin";
23const char kAllowTopNavigation[] = "allow-top-navigation";
24
25struct 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
37bool 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|.
91bool 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
106bool 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
115bool 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
157bool 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}  // namespace csp_validator
199
200}  // namespace extensions
201