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