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/manifest_handlers/csp_info.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/no_destructor.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/values.h"
14 #include "extensions/common/csp_validator.h"
15 #include "extensions/common/error_utils.h"
16 #include "extensions/common/install_warning.h"
17 #include "extensions/common/manifest_constants.h"
18 #include "extensions/common/manifest_handlers/sandboxed_page_info.h"
19 
20 namespace extensions {
21 
22 namespace keys = manifest_keys;
23 namespace errors = manifest_errors;
24 
25 using csp_validator::ContentSecurityPolicyIsLegal;
26 using csp_validator::SanitizeContentSecurityPolicy;
27 
28 namespace {
29 
30 const char kDefaultContentSecurityPolicy[] =
31     "script-src 'self' blob: filesystem:; "
32     "object-src 'self' blob: filesystem:;";
33 
34 // The default secure CSP to be used in order to prevent remote scripts.
35 const char kDefaultSecureCSP[] = "script-src 'self'; object-src 'self';";
36 
37 const char kDefaultSandboxedPageContentSecurityPolicy[] =
38     "sandbox allow-scripts allow-forms allow-popups allow-modals; "
39     "script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';";
40 
41 #define PLATFORM_APP_LOCAL_CSP_SOURCES "'self' blob: filesystem: data:"
42 
43 // clang-format off
44 const char kDefaultPlatformAppContentSecurityPolicy[] =
45     // Platform apps can only use local resources by default.
46     "default-src 'self' blob: filesystem:;"
47     // For remote resources, they can fetch them via XMLHttpRequest.
48     " connect-src * data: blob: filesystem:;"
49     // And serve them via data: or same-origin (blob:, filesystem:) URLs
50     " style-src " PLATFORM_APP_LOCAL_CSP_SOURCES " 'unsafe-inline';"
51     " img-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
52     " frame-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
53     " font-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
54     // Media can be loaded from remote resources since:
55     // 1. <video> and <audio> have good fallback behavior when offline or under
56     //    spotty connectivity.
57     // 2. Fetching via XHR and serving via blob: URLs currently does not allow
58     //    streaming or partial buffering.
59     " media-src * data: blob: filesystem:;"
60     // Scripts are allowed to use WebAssembly
61     " script-src 'self' blob: filesystem: 'wasm-eval';";
62 // clang-format on
63 
GetValidatorOptions(Extension * extension)64 int GetValidatorOptions(Extension* extension) {
65   int options = csp_validator::OPTIONS_NONE;
66 
67   // crbug.com/146487
68   if (extension->GetType() == Manifest::TYPE_EXTENSION ||
69       extension->GetType() == Manifest::TYPE_LEGACY_PACKAGED_APP) {
70     options |= csp_validator::OPTIONS_ALLOW_UNSAFE_EVAL;
71   }
72 
73   // Component extensions can specify an insecure object-src directive. This
74   // should be safe because non-NPAPI plugins should load in a sandboxed process
75   // and only allow communication via postMessage. Flash is an exception since
76   // it allows scripting into the embedder page, but even then it should
77   // disallow cross-origin scripting. At some point we may want to consider
78   // allowing this publicly.
79   if (extensions::Manifest::IsComponentLocation(extension->location()))
80     options |= csp_validator::OPTIONS_ALLOW_INSECURE_OBJECT_SRC;
81 
82   return options;
83 }
84 
GetInvalidManifestKeyError(base::StringPiece key)85 base::string16 GetInvalidManifestKeyError(base::StringPiece key) {
86   return ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidManifestKey, key);
87 }
88 
89 // Returns null if the manifest type can't access the path. Else returns the
90 // corresponding Value.
GetManifestPath(const Extension * extension,const char * path)91 const base::Value* GetManifestPath(const Extension* extension,
92                                    const char* path) {
93   const base::Value* value = nullptr;
94   return extension->manifest()->Get(path, &value) ? value : nullptr;
95 }
96 
GetDefaultExtensionPagesCSP(Extension * extension,bool secure_only)97 const char* GetDefaultExtensionPagesCSP(Extension* extension,
98                                         bool secure_only) {
99   if (secure_only)
100     return kDefaultSecureCSP;
101 
102   if (extension->GetType() == Manifest::TYPE_PLATFORM_APP)
103     return kDefaultPlatformAppContentSecurityPolicy;
104 
105   return kDefaultContentSecurityPolicy;
106 }
107 
108 }  // namespace
109 
CSPInfo(std::string extension_pages_csp)110 CSPInfo::CSPInfo(std::string extension_pages_csp)
111     : extension_pages_csp(std::move(extension_pages_csp)) {}
112 
~CSPInfo()113 CSPInfo::~CSPInfo() {
114 }
115 
116 // static
GetExtensionPagesCSP(const Extension * extension)117 const std::string& CSPInfo::GetExtensionPagesCSP(const Extension* extension) {
118   CSPInfo* csp_info = static_cast<CSPInfo*>(
119           extension->GetManifestData(keys::kContentSecurityPolicy));
120   return csp_info ? csp_info->extension_pages_csp : base::EmptyString();
121 }
122 
123 // static
GetIsolatedWorldCSP(const Extension & extension)124 const std::string* CSPInfo::GetIsolatedWorldCSP(const Extension& extension) {
125   if (extension.manifest_version() >= 3) {
126     // The isolated world will use its own CSP which blocks remotely hosted
127     // code.
128     static const base::NoDestructor<std::string> default_isolated_world_csp(
129         kDefaultSecureCSP);
130     return default_isolated_world_csp.get();
131   }
132 
133   Manifest::Type type = extension.GetType();
134   bool bypass_main_world_csp = type == Manifest::TYPE_PLATFORM_APP ||
135                                type == Manifest::TYPE_EXTENSION ||
136                                type == Manifest::TYPE_LEGACY_PACKAGED_APP;
137   if (!bypass_main_world_csp) {
138     // The isolated world will use the main world CSP.
139     return nullptr;
140   }
141 
142   // The isolated world will bypass the main world CSP.
143   return &base::EmptyString();
144 }
145 
146 // static
GetSandboxContentSecurityPolicy(const Extension * extension)147 const std::string& CSPInfo::GetSandboxContentSecurityPolicy(
148     const Extension* extension) {
149   CSPInfo* csp_info = static_cast<CSPInfo*>(
150       extension->GetManifestData(keys::kContentSecurityPolicy));
151   return csp_info ? csp_info->sandbox_csp : base::EmptyString();
152 }
153 
154 // static
GetResourceContentSecurityPolicy(const Extension * extension,const std::string & relative_path)155 const std::string& CSPInfo::GetResourceContentSecurityPolicy(
156     const Extension* extension,
157     const std::string& relative_path) {
158   return SandboxedPageInfo::IsSandboxedPage(extension, relative_path)
159              ? GetSandboxContentSecurityPolicy(extension)
160              : GetExtensionPagesCSP(extension);
161 }
162 
163 CSPHandler::CSPHandler() = default;
164 
165 CSPHandler::~CSPHandler() = default;
166 
Parse(Extension * extension,base::string16 * error)167 bool CSPHandler::Parse(Extension* extension, base::string16* error) {
168   const char* key = extension->GetType() == Manifest::TYPE_PLATFORM_APP
169                         ? keys::kPlatformAppContentSecurityPolicy
170                         : keys::kContentSecurityPolicy;
171 
172   // The "content_security_policy" manifest key can either be a string or a
173   // dictionary of the format.
174   // "content_security_policy" : {
175   //     "extension_pages": "",
176   //     "sandbox": "",
177   //  }
178   // The dictionary is supported (and mandated) for manifest v3 (and above)
179   // extensions.
180   const base::Value* csp = GetManifestPath(extension, key);
181   bool parse_as_dictionary = extension->manifest_version() >= 3;
182   if (parse_as_dictionary) {
183     if (csp && !csp->is_dict()) {
184       *error = GetInvalidManifestKeyError(key);
185       return false;
186     }
187     return ParseCSPDictionary(extension, error);
188   }
189 
190   if (!ParseExtensionPagesCSP(extension, error, key, false /* secure_only */,
191                               csp)) {
192     return false;
193   }
194 
195   if (!ParseSandboxCSP(extension, error, keys::kSandboxedPagesCSP,
196                        GetManifestPath(extension, keys::kSandboxedPagesCSP))) {
197     return false;
198   }
199 
200   return true;
201 }
202 
ParseCSPDictionary(Extension * extension,base::string16 * error)203 bool CSPHandler::ParseCSPDictionary(Extension* extension,
204                                     base::string16* error) {
205   // keys::kSandboxedPagesCSP shouldn't be used when using
206   // keys::kContentSecurityPolicy as a dictionary.
207   if (extension->manifest()->HasPath(keys::kSandboxedPagesCSP)) {
208     *error = base::ASCIIToUTF16(errors::kSandboxPagesCSPKeyNotAllowed);
209     return false;
210   }
211 
212   return ParseExtensionPagesCSP(
213              extension, error, keys::kContentSecurityPolicy_ExtensionPagesPath,
214              true /* secure_only */,
215              GetManifestPath(
216                  extension, keys::kContentSecurityPolicy_ExtensionPagesPath)) &&
217          ParseSandboxCSP(
218              extension, error, keys::kContentSecurityPolicy_SandboxedPagesPath,
219              GetManifestPath(extension,
220                              keys::kContentSecurityPolicy_SandboxedPagesPath));
221 }
222 
ParseExtensionPagesCSP(Extension * extension,base::string16 * error,base::StringPiece manifest_key,bool secure_only,const base::Value * content_security_policy)223 bool CSPHandler::ParseExtensionPagesCSP(
224     Extension* extension,
225     base::string16* error,
226     base::StringPiece manifest_key,
227     bool secure_only,
228     const base::Value* content_security_policy) {
229   if (!content_security_policy) {
230     return SetExtensionPagesCSP(
231         extension, manifest_key, secure_only,
232         GetDefaultExtensionPagesCSP(extension, secure_only));
233   }
234 
235   if (!content_security_policy->is_string()) {
236     *error = GetInvalidManifestKeyError(manifest_key);
237     return false;
238   }
239 
240   const std::string& content_security_policy_str =
241       content_security_policy->GetString();
242   if (!ContentSecurityPolicyIsLegal(content_security_policy_str)) {
243     *error = GetInvalidManifestKeyError(manifest_key);
244     return false;
245   }
246 
247   if (secure_only) {
248     if (!csp_validator::DoesCSPDisallowRemoteCode(content_security_policy_str,
249                                                   manifest_key, error)) {
250       return false;
251     }
252     SetExtensionPagesCSP(extension, manifest_key, secure_only,
253                          content_security_policy_str);
254     return true;
255   }
256 
257   std::vector<InstallWarning> warnings;
258   std::string sanitized_content_security_policy = SanitizeContentSecurityPolicy(
259       content_security_policy_str, manifest_key.as_string(),
260       GetValidatorOptions(extension), &warnings);
261   extension->AddInstallWarnings(std::move(warnings));
262 
263   SetExtensionPagesCSP(extension, manifest_key, secure_only,
264                        std::move(sanitized_content_security_policy));
265   return true;
266 }
267 
ParseSandboxCSP(Extension * extension,base::string16 * error,base::StringPiece manifest_key,const base::Value * sandbox_csp)268 bool CSPHandler::ParseSandboxCSP(Extension* extension,
269                                  base::string16* error,
270                                  base::StringPiece manifest_key,
271                                  const base::Value* sandbox_csp) {
272   if (!sandbox_csp) {
273     SetSandboxCSP(extension, kDefaultSandboxedPageContentSecurityPolicy);
274     return true;
275   }
276 
277   if (!sandbox_csp->is_string()) {
278     *error = GetInvalidManifestKeyError(manifest_key);
279     return false;
280   }
281 
282   const std::string& sandbox_csp_str = sandbox_csp->GetString();
283   if (!ContentSecurityPolicyIsLegal(sandbox_csp_str) ||
284       !csp_validator::ContentSecurityPolicyIsSandboxed(sandbox_csp_str,
285                                                        extension->GetType())) {
286     *error = GetInvalidManifestKeyError(manifest_key);
287     return false;
288   }
289 
290   std::vector<InstallWarning> warnings;
291   std::string effective_sandbox_csp =
292       csp_validator::GetEffectiveSandoxedPageCSP(
293           sandbox_csp_str, manifest_key.as_string(), &warnings);
294   SetSandboxCSP(extension, std::move(effective_sandbox_csp));
295   extension->AddInstallWarnings(std::move(warnings));
296   return true;
297 }
298 
SetExtensionPagesCSP(Extension * extension,base::StringPiece manifest_key,bool secure_only,std::string content_security_policy)299 bool CSPHandler::SetExtensionPagesCSP(Extension* extension,
300                                       base::StringPiece manifest_key,
301                                       bool secure_only,
302                                       std::string content_security_policy) {
303   if (secure_only) {
304     base::string16 error;
305     DCHECK(csp_validator::DoesCSPDisallowRemoteCode(content_security_policy,
306                                                     manifest_key, &error));
307   } else {
308     DCHECK_EQ(content_security_policy,
309               SanitizeContentSecurityPolicy(
310                   content_security_policy, manifest_key.as_string(),
311                   GetValidatorOptions(extension), nullptr));
312   }
313 
314   extension->SetManifestData(
315       keys::kContentSecurityPolicy,
316       std::make_unique<CSPInfo>(std::move(content_security_policy)));
317   return true;
318 }
319 
SetSandboxCSP(Extension * extension,std::string sandbox_csp)320 void CSPHandler::SetSandboxCSP(Extension* extension, std::string sandbox_csp) {
321   CHECK(csp_validator::ContentSecurityPolicyIsSandboxed(sandbox_csp,
322                                                         extension->GetType()));
323 
324   // By now we must have parsed the extension page CSP.
325   CSPInfo* csp_info = static_cast<CSPInfo*>(
326       extension->GetManifestData(keys::kContentSecurityPolicy));
327   DCHECK(csp_info);
328   csp_info->sandbox_csp = std::move(sandbox_csp);
329 }
330 
AlwaysParseForType(Manifest::Type type) const331 bool CSPHandler::AlwaysParseForType(Manifest::Type type) const {
332   // TODO(crbug.com/1005978): Check if TYPE_USER_SCRIPT needs to be included
333   // here.
334   return type == Manifest::TYPE_PLATFORM_APP ||
335          type == Manifest::TYPE_EXTENSION ||
336          type == Manifest::TYPE_LEGACY_PACKAGED_APP;
337 }
338 
Keys() const339 base::span<const char* const> CSPHandler::Keys() const {
340   static constexpr const char* kKeys[] = {
341       keys::kContentSecurityPolicy, keys::kPlatformAppContentSecurityPolicy,
342       keys::kSandboxedPagesCSP};
343   return kKeys;
344 }
345 
346 }  // namespace extensions
347