1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* A namespace class for static content security utilities. */
9 #include "nsContentSecurityUtils.h"
11 #include "mozilla/Components.h"
12 #include "mozilla/dom/nsMixedContentBlocker.h"
13 #include "mozilla/dom/ScriptSettings.h"
14 #include "mozilla/dom/WorkerCommon.h"
15 #include "mozilla/dom/WorkerPrivate.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsIContentSecurityPolicy.h"
18 #include "nsIChannel.h"
19 #include "nsIHttpChannel.h"
20 #include "nsIMultiPartChannel.h"
22 #include "nsITransfer.h"
23 #include "nsNetUtil.h"
24 #include "nsSandboxFlags.h"
26 # include "mozilla/WinHeaderOnlyUtils.h"
27 # include "WinUtils.h"
31 #include "FramingChecker.h"
32 #include "js/Array.h" // JS::GetArrayLength
33 #include "js/ContextOptions.h"
34 #include "js/PropertyAndElement.h" // JS_GetElement
35 #include "js/RegExp.h"
36 #include "js/RegExpFlags.h" // JS::RegExpFlags
37 #include "js/friend/ErrorMessages.h" // JSMSG_UNSAFE_FILENAME
38 #include "mozilla/ExtensionPolicyService.h"
39 #include "mozilla/Logging.h"
40 #include "mozilla/Preferences.h"
41 #include "mozilla/dom/Document.h"
42 #include "mozilla/dom/nsCSPContext.h"
43 #include "mozilla/glean/DomSecurityMetrics.h"
44 #include "mozilla/StaticPrefs_security.h"
46 #include "mozilla/StaticPrefs_extensions.h"
47 #include "mozilla/StaticPrefs_dom.h"
48 #include "nsIConsoleService.h"
49 #include "nsIStringBundle.h"
51 using namespace mozilla
;
52 using namespace mozilla::dom
;
54 extern mozilla::LazyLogModule sCSMLog
;
55 extern Atomic
<bool, mozilla::Relaxed
> sJSHacksChecked
;
56 extern Atomic
<bool, mozilla::Relaxed
> sJSHacksPresent
;
57 extern Atomic
<bool, mozilla::Relaxed
> sCSSHacksChecked
;
58 extern Atomic
<bool, mozilla::Relaxed
> sCSSHacksPresent
;
60 // Helper function for IsConsideredSameOriginForUIR which makes
61 // Principals of scheme 'http' return Principals of scheme 'https'.
62 static already_AddRefed
<nsIPrincipal
> MakeHTTPPrincipalHTTPS(
63 nsIPrincipal
* aPrincipal
) {
64 nsCOMPtr
<nsIPrincipal
> principal
= aPrincipal
;
65 // if the principal is not http, then it can also not be upgraded
67 if (!principal
->SchemeIs("http")) {
68 return principal
.forget();
72 aPrincipal
->GetAsciiSpec(spec
);
73 // replace http with https
74 spec
.ReplaceLiteral(0, 4, "https");
76 nsCOMPtr
<nsIURI
> newURI
;
77 nsresult rv
= NS_NewURI(getter_AddRefs(newURI
), spec
);
78 if (NS_WARN_IF(NS_FAILED(rv
))) {
82 mozilla::OriginAttributes OA
=
83 BasePrincipal::Cast(aPrincipal
)->OriginAttributesRef();
85 principal
= BasePrincipal::CreateContentPrincipal(newURI
, OA
);
86 return principal
.forget();
90 bool nsContentSecurityUtils::IsConsideredSameOriginForUIR(
91 nsIPrincipal
* aTriggeringPrincipal
, nsIPrincipal
* aResultPrincipal
) {
92 MOZ_ASSERT(aTriggeringPrincipal
);
93 MOZ_ASSERT(aResultPrincipal
);
94 // we only have to make sure that the following truth table holds:
95 // aTriggeringPrincipal | aResultPrincipal | Result
96 // ----------------------------------------------------------------
97 // http://example.com/foo.html | http://example.com/bar.html | true
98 // http://example.com/foo.html | https://example.com/bar.html | true
99 // https://example.com/foo.html | https://example.com/bar.html | true
100 // https://example.com/foo.html | http://example.com/bar.html | true
102 // fast path if both principals are same-origin
103 if (aTriggeringPrincipal
->Equals(aResultPrincipal
)) {
107 // in case a principal uses a scheme of 'http' then we just upgrade to
108 // 'https' and use the principal equals comparison operator to check
110 nsCOMPtr
<nsIPrincipal
> compareTriggeringPrincipal
=
111 MakeHTTPPrincipalHTTPS(aTriggeringPrincipal
);
113 nsCOMPtr
<nsIPrincipal
> compareResultPrincipal
=
114 MakeHTTPPrincipalHTTPS(aResultPrincipal
);
116 return compareTriggeringPrincipal
->Equals(compareResultPrincipal
);
120 * Performs a Regular Expression match, optionally returning the results.
121 * This function is not safe to use OMT.
123 * @param aPattern The regex pattern
124 * @param aString The string to compare against
125 * @param aOnlyMatch Whether we want match results or only a true/false for
127 * @param aMatchResult Out param for whether or not the pattern matched
128 * @param aRegexResults Out param for the matches of the regex, if requested
129 * @returns nsresult indicating correct function operation or error
131 nsresult
RegexEval(const nsAString
& aPattern
, const nsAString
& aString
,
132 bool aOnlyMatch
, bool& aMatchResult
,
133 nsTArray
<nsString
>* aRegexResults
= nullptr) {
134 MOZ_ASSERT(NS_IsMainThread());
135 aMatchResult
= false;
137 mozilla::dom::AutoJSAPI jsapi
;
140 JSContext
* cx
= jsapi
.cx();
141 mozilla::AutoDisableJSInterruptCallback
disabler(cx
);
143 // We can use the junk scope here, because we're just using it for regexp
144 // evaluation, not actual script execution, and we disable statics so that the
145 // evaluation does not interact with the execution global.
146 JSAutoRealm
ar(cx
, xpc::PrivilegedJunkScope());
148 JS::Rooted
<JSObject
*> regexp(
149 cx
, JS::NewUCRegExpObject(cx
, aPattern
.BeginReading(), aPattern
.Length(),
150 JS::RegExpFlag::Unicode
));
152 return NS_ERROR_ILLEGAL_VALUE
;
155 JS::Rooted
<JS::Value
> regexResult(cx
, JS::NullValue());
158 if (!JS::ExecuteRegExpNoStatics(cx
, regexp
, aString
.BeginReading(),
159 aString
.Length(), &index
, aOnlyMatch
,
161 return NS_ERROR_FAILURE
;
164 if (regexResult
.isNull()) {
165 // On no match, ExecuteRegExpNoStatics returns Null
169 // On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean
171 MOZ_ASSERT(regexResult
.isBoolean() && regexResult
.toBoolean());
175 if (aRegexResults
== nullptr) {
176 return NS_ERROR_INVALID_ARG
;
179 // Now we know we have a result, and we need to extract it so we can read it.
181 JS::Rooted
<JSObject
*> regexResultObj(cx
, ®exResult
.toObject());
182 if (!JS::GetArrayLength(cx
, regexResultObj
, &length
)) {
183 return NS_ERROR_NOT_AVAILABLE
;
185 MOZ_LOG(sCSMLog
, LogLevel::Verbose
, ("Regex Matched %i strings", length
));
187 for (uint32_t i
= 0; i
< length
; i
++) {
188 JS::Rooted
<JS::Value
> element(cx
);
189 if (!JS_GetElement(cx
, regexResultObj
, i
, &element
)) {
190 return NS_ERROR_NO_CONTENT
;
193 nsAutoJSString value
;
194 if (!value
.init(cx
, element
)) {
195 return NS_ERROR_NO_CONTENT
;
198 MOZ_LOG(sCSMLog
, LogLevel::Verbose
,
199 ("Regex Matching: %i: %s", i
, NS_ConvertUTF16toUTF8(value
).get()));
200 aRegexResults
->AppendElement(value
);
208 * MOZ_CRASH_UNSAFE_PRINTF has a sPrintfCrashReasonSize-sized buffer. We need
209 * to make sure we don't exceed it. These functions perform this check and
210 * munge things for us.
215 * Destructively truncates a string to fit within the limit
217 char* nsContentSecurityUtils::SmartFormatCrashString(const char* str
) {
218 return nsContentSecurityUtils::SmartFormatCrashString(strdup(str
));
221 char* nsContentSecurityUtils::SmartFormatCrashString(char* str
) {
222 auto str_len
= strlen(str
);
224 if (str_len
> sPrintfCrashReasonSize
) {
225 str
[sPrintfCrashReasonSize
- 1] = '\0';
226 str_len
= strlen(str
);
228 MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize
> str_len
);
234 * Destructively truncates two strings to fit within the limit.
235 * format_string is a format string containing two %s entries
236 * The second string will be truncated to the _last_ 25 characters
237 * The first string will be truncated to the remaining limit.
239 nsCString
nsContentSecurityUtils::SmartFormatCrashString(
240 const char* part1
, const char* part2
, const char* format_string
) {
241 return SmartFormatCrashString(strdup(part1
), strdup(part2
), format_string
);
244 nsCString
nsContentSecurityUtils::SmartFormatCrashString(
245 char* part1
, char* part2
, const char* format_string
) {
246 auto part1_len
= strlen(part1
);
247 auto part2_len
= strlen(part2
);
249 auto constant_len
= strlen(format_string
) - 4;
251 if (part1_len
+ part2_len
+ constant_len
> sPrintfCrashReasonSize
) {
252 if (part2_len
> 25) {
253 part2
+= (part2_len
- 25);
255 part2_len
= strlen(part2
);
257 part1
[sPrintfCrashReasonSize
- (constant_len
+ part2_len
+ 1)] = '\0';
258 part1_len
= strlen(part1
);
260 MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize
>
261 constant_len
+ part1_len
+ part2_len
);
263 auto parts
= nsPrintfCString(format_string
, part1
, part2
);
264 return std::move(parts
);
268 * Telemetry Events extra data only supports 80 characters, so we optimize the
269 * filename to be smaller and collect more data.
271 nsCString
OptimizeFileName(const nsAString
& aFileName
) {
272 nsCString optimizedName
;
273 CopyUTF16toUTF8(aFileName
, optimizedName
);
275 MOZ_LOG(sCSMLog
, LogLevel::Verbose
,
276 ("Optimizing FileName: %s", optimizedName
.get()));
278 optimizedName
.ReplaceSubstring(".xpi!"_ns
, "!"_ns
);
279 optimizedName
.ReplaceSubstring("shield.mozilla.org!"_ns
, "s!"_ns
);
280 optimizedName
.ReplaceSubstring("mozilla.org!"_ns
, "m!"_ns
);
281 if (optimizedName
.Length() > 80) {
282 optimizedName
.Truncate(80);
285 MOZ_LOG(sCSMLog
, LogLevel::Verbose
,
286 ("Optimized FileName: %s", optimizedName
.get()));
287 return optimizedName
;
291 * FilenameToFilenameType takes a fileName and returns a Pair of strings.
292 * The First entry is a string indicating the type of fileName
293 * The Second entry is a Maybe<string> that can contain additional details to
296 * The reason we use strings (instead of an int/enum) is because the Telemetry
297 * Events API only accepts strings.
299 * Function is a static member of the class to enable gtests.
303 FilenameTypeAndDetails
nsContentSecurityUtils::FilenameToFilenameType(
304 const nsACString
& fileName
, bool collectAdditionalExtensionData
) {
305 // These are strings because the Telemetry Events API only accepts strings
306 static constexpr auto kChromeURI
= "chromeuri"_ns
;
307 static constexpr auto kResourceURI
= "resourceuri"_ns
;
308 static constexpr auto kBlobUri
= "bloburi"_ns
;
309 static constexpr auto kDataUri
= "dataurl"_ns
;
310 static constexpr auto kAboutUri
= "abouturi"_ns
;
311 static constexpr auto kDataUriWebExtCStyle
=
312 "dataurl-extension-contentstyle"_ns
;
313 static constexpr auto kSingleString
= "singlestring"_ns
;
314 static constexpr auto kMozillaExtensionFile
= "mozillaextension_file"_ns
;
315 static constexpr auto kOtherExtensionFile
= "otherextension_file"_ns
;
316 static constexpr auto kExtensionURI
= "extension_uri"_ns
;
317 static constexpr auto kSuspectedUserChromeJS
= "suspectedUserChromeJS"_ns
;
319 static constexpr auto kSanitizedWindowsURL
= "sanitizedWindowsURL"_ns
;
320 static constexpr auto kSanitizedWindowsPath
= "sanitizedWindowsPath"_ns
;
322 static constexpr auto kOther
= "other"_ns
;
323 static constexpr auto kOtherWorker
= "other-on-worker"_ns
;
324 static constexpr auto kRegexFailure
= "regexfailure"_ns
;
326 static constexpr auto kUCJSRegex
= u
"(.+).uc.js\\?*[0-9]*$"_ns
;
327 static constexpr auto kExtensionRegex
= u
"extensions/(.+)@(.+)!(.+)$"_ns
;
328 static constexpr auto kSingleFileRegex
= u
"^[a-zA-Z0-9.?]+$"_ns
;
330 if (fileName
.IsEmpty()) {
331 return FilenameTypeAndDetails(kOther
, Nothing());
334 // resource:// and chrome://
335 // These don't contain any user (profile) paths.
336 if (StringBeginsWith(fileName
, "chrome://"_ns
)) {
337 if (StringBeginsWith(fileName
, "chrome://userscripts/"_ns
) ||
338 StringBeginsWith(fileName
, "chrome://userchromejs/"_ns
) ||
339 StringBeginsWith(fileName
, "chrome://tabmix"_ns
) ||
340 StringBeginsWith(fileName
, "chrome://searchwp/"_ns
)) {
341 return FilenameTypeAndDetails(kSuspectedUserChromeJS
,
342 Some(nsCString(fileName
)));
344 return FilenameTypeAndDetails(kChromeURI
, Some(nsCString(fileName
)));
346 if (StringBeginsWith(fileName
, "resource://"_ns
)) {
347 if (StringBeginsWith(fileName
, "resource://usl-ucjs/"_ns
) ||
348 StringBeginsWith(fileName
, "resource://sfm-ucjs/"_ns
) ||
349 StringBeginsWith(fileName
, "resource://cpmanager-legacy/"_ns
)) {
350 return FilenameTypeAndDetails(kSuspectedUserChromeJS
,
351 Some(nsCString(fileName
)));
353 return FilenameTypeAndDetails(kResourceURI
, Some(nsCString(fileName
)));
357 if (StringBeginsWith(fileName
, "blob:"_ns
)) {
358 return FilenameTypeAndDetails(kBlobUri
, Nothing());
360 if (StringBeginsWith(fileName
, "data:text/css;extension=style;"_ns
)) {
361 return FilenameTypeAndDetails(kDataUriWebExtCStyle
, Nothing());
363 if (StringBeginsWith(fileName
, "data:"_ns
)) {
364 return FilenameTypeAndDetails(kDataUri
, Nothing());
367 // Can't do regex matching off-main-thread
368 if (NS_IsMainThread()) {
369 NS_ConvertUTF8toUTF16
fileNameA(fileName
);
370 // Extension as loaded via a file://
372 nsTArray
<nsString
> regexResults
;
374 RegexEval(kExtensionRegex
, fileNameA
,
375 /* aOnlyMatch = */ false, regexMatch
, ®exResults
);
377 return FilenameTypeAndDetails(kRegexFailure
, Nothing());
380 nsCString type
= StringEndsWith(regexResults
[2], u
"mozilla.org.xpi"_ns
)
381 ? kMozillaExtensionFile
382 : kOtherExtensionFile
;
383 const auto& extensionNameAndPath
=
384 Substring(regexResults
[0], std::size("extensions/") - 1);
385 return FilenameTypeAndDetails(
386 type
, Some(OptimizeFileName(extensionNameAndPath
)));
390 rv
= RegexEval(kSingleFileRegex
, fileNameA
, /* aOnlyMatch = */ true,
393 return FilenameTypeAndDetails(kRegexFailure
, Nothing());
396 return FilenameTypeAndDetails(kSingleString
, Some(nsCString(fileName
)));
399 // Suspected userChromeJS script
400 rv
= RegexEval(kUCJSRegex
, fileNameA
, /* aOnlyMatch = */ true, regexMatch
);
402 return FilenameTypeAndDetails(kRegexFailure
, Nothing());
405 return FilenameTypeAndDetails(kSuspectedUserChromeJS
, Nothing());
409 // Something loaded via an about:// URI.
410 if (StringBeginsWith(fileName
, "about:"_ns
)) {
411 // Remove any querystrings and such
412 long int desired_length
= fileName
.Length();
413 long int possible_new_length
= 0;
415 possible_new_length
= fileName
.FindChar('?');
416 if (possible_new_length
!= -1 && possible_new_length
< desired_length
) {
417 desired_length
= possible_new_length
;
420 possible_new_length
= fileName
.FindChar('#');
421 if (possible_new_length
!= -1 && possible_new_length
< desired_length
) {
422 desired_length
= possible_new_length
;
425 auto subFileName
= Substring(fileName
, 0, desired_length
);
427 return FilenameTypeAndDetails(kAboutUri
, Some(nsCString(subFileName
)));
430 // Something loaded via a moz-extension:// URI.
431 if (StringBeginsWith(fileName
, "moz-extension://"_ns
)) {
432 if (!collectAdditionalExtensionData
) {
433 return FilenameTypeAndDetails(kExtensionURI
, Nothing());
436 nsAutoCString sanitizedPathAndScheme
;
437 sanitizedPathAndScheme
.Append("moz-extension://["_ns
);
439 nsCOMPtr
<nsIURI
> uri
;
440 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), fileName
);
442 // Return after adding ://[ so we know we failed here.
443 return FilenameTypeAndDetails(kExtensionURI
,
444 Some(sanitizedPathAndScheme
));
447 mozilla::extensions::URLInfo
url(uri
);
448 if (NS_IsMainThread()) {
449 // EPS is only usable on main thread
451 ExtensionPolicyService::GetSingleton().GetByHost(url
.Host());
454 policy
->GetId(addOnId
);
456 sanitizedPathAndScheme
.Append(NS_ConvertUTF16toUTF8(addOnId
));
457 sanitizedPathAndScheme
.Append(": "_ns
);
458 sanitizedPathAndScheme
.Append(NS_ConvertUTF16toUTF8(policy
->Name()));
459 sanitizedPathAndScheme
.Append("]"_ns
);
461 if (policy
->IsPrivileged()) {
462 sanitizedPathAndScheme
.Append("P=1"_ns
);
464 sanitizedPathAndScheme
.Append("P=0"_ns
);
467 sanitizedPathAndScheme
.Append("failed finding addon by host]"_ns
);
470 sanitizedPathAndScheme
.Append("can't get addon off main thread]"_ns
);
473 sanitizedPathAndScheme
.Append(url
.FilePath());
474 return FilenameTypeAndDetails(kExtensionURI
, Some(sanitizedPathAndScheme
));
478 auto flags
= mozilla::widget::WinUtils::PathTransformFlags::Default
|
479 mozilla::widget::WinUtils::PathTransformFlags::RequireFilePath
;
480 const NS_ConvertUTF8toUTF16
fileNameA(fileName
);
481 nsAutoString
strSanitizedPath(fileNameA
);
482 if (widget::WinUtils::PreparePathForTelemetry(strSanitizedPath
, flags
)) {
483 DWORD cchDecodedUrl
= INTERNET_MAX_URL_LENGTH
;
484 WCHAR szOut
[INTERNET_MAX_URL_LENGTH
];
486 SAFECALL_URLMON_FUNC(CoInternetParseUrl
, fileNameA
.get(), PARSE_SCHEMA
, 0,
487 szOut
, INTERNET_MAX_URL_LENGTH
, &cchDecodedUrl
, 0);
488 if (hr
== S_OK
&& cchDecodedUrl
) {
489 nsAutoString sanitizedPathAndScheme
;
490 sanitizedPathAndScheme
.Append(szOut
);
491 if (sanitizedPathAndScheme
== u
"file"_ns
) {
492 sanitizedPathAndScheme
.Append(u
"://.../"_ns
);
493 sanitizedPathAndScheme
.Append(strSanitizedPath
);
495 return FilenameTypeAndDetails(
496 kSanitizedWindowsURL
,
497 Some(NS_ConvertUTF16toUTF8(sanitizedPathAndScheme
)));
499 return FilenameTypeAndDetails(
500 kSanitizedWindowsPath
, Some(NS_ConvertUTF16toUTF8(strSanitizedPath
)));
505 if (!NS_IsMainThread()) {
506 return FilenameTypeAndDetails(kOtherWorker
, Nothing());
508 return FilenameTypeAndDetails(kOther
, Nothing());
511 #if defined(EARLY_BETA_OR_EARLIER)
512 // Crash String must be safe from a telemetry point of view.
513 // This will be ensured when this function is used.
514 void PossiblyCrash(const char* aPrefSuffix
, const char* aUnsafeCrashString
,
515 const nsCString
& aSafeCrashString
) {
516 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
517 // We only crash in the parent (unfortunately) because it's
518 // the only place we can be sure that our only-crash-once
519 // pref-writing works.
522 if (!NS_IsMainThread()) {
523 // Setting a pref off the main thread causes ContentParent to observe the
524 // pref set, resulting in a Release Assertion when it tries to update the
525 // child off main thread. So don't do any of this off main thread. (Which
526 // is a bit of a blind spot for this purpose...)
530 nsCString
previous_crashes("security.crash_tracking.");
531 previous_crashes
.Append(aPrefSuffix
);
532 previous_crashes
.Append(".prevCrashes");
534 nsCString
max_crashes("security.crash_tracking.");
535 max_crashes
.Append(aPrefSuffix
);
536 max_crashes
.Append(".maxCrashes");
538 int32_t numberOfPreviousCrashes
= 0;
539 numberOfPreviousCrashes
= Preferences::GetInt(previous_crashes
.get(), 0);
541 int32_t maxAllowableCrashes
= 0;
542 maxAllowableCrashes
= Preferences::GetInt(max_crashes
.get(), 0);
544 if (numberOfPreviousCrashes
>= maxAllowableCrashes
) {
549 Preferences::SetInt(previous_crashes
.get(), ++numberOfPreviousCrashes
);
554 nsCOMPtr
<nsIPrefService
> prefsCom
= Preferences::GetService();
555 Preferences
* prefs
= static_cast<Preferences
*>(prefsCom
.get());
557 if (!prefs
->AllowOffMainThreadSave()) {
558 // Do not crash if we can't save prefs off the main thread
562 rv
= prefs
->SavePrefFileBlocking();
563 if (!NS_FAILED(rv
)) {
564 // We can only use this in local builds where we don't send stuff up to the
565 // crash reporter because it has user private data.
566 // MOZ_CRASH_UNSAFE_PRINTF("%s",
567 // nsContentSecurityUtils::SmartFormatCrashString(aUnsafeCrashString));
568 MOZ_CRASH_UNSAFE_PRINTF(
570 nsContentSecurityUtils::SmartFormatCrashString(aSafeCrashString
.get()));
575 class EvalUsageNotificationRunnable final
: public Runnable
{
577 EvalUsageNotificationRunnable(bool aIsSystemPrincipal
,
578 const nsACString
& aFileName
, uint64_t aWindowID
,
579 uint32_t aLineNumber
, uint32_t aColumnNumber
)
580 : mozilla::Runnable("EvalUsageNotificationRunnable"),
581 mIsSystemPrincipal(aIsSystemPrincipal
),
582 mFileName(aFileName
),
583 mWindowID(aWindowID
),
584 mLineNumber(aLineNumber
),
585 mColumnNumber(aColumnNumber
) {}
587 NS_IMETHOD
Run() override
{
588 nsContentSecurityUtils::NotifyEvalUsage(
589 mIsSystemPrincipal
, mFileName
, mWindowID
, mLineNumber
, mColumnNumber
);
596 bool mIsSystemPrincipal
;
599 uint32_t mLineNumber
;
600 uint32_t mColumnNumber
;
604 bool nsContentSecurityUtils::IsEvalAllowed(JSContext
* cx
,
605 bool aIsSystemPrincipal
,
606 const nsAString
& aScript
) {
607 // This allowlist contains files that are permanently allowed to use
608 // eval()-like functions. It will ideally be restricted to files that are
609 // exclusively used in testing contexts.
610 static nsLiteralCString evalAllowlist
[] = {
611 // Test-only third-party library
612 "resource://testing-common/sinon-7.2.7.js"_ns
,
614 "resource://testing-common/content-task.js"_ns
,
616 // Tracked by Bug 1584605
617 "resource://gre/modules/translation/cld-worker.js"_ns
,
619 // require.js implements a script loader for workers. It uses eval
620 // to load the script; but injection is only possible in situations
621 // that you could otherwise control script that gets executed, so
622 // it is okay to allow eval() as it adds no additional attack surface.
623 // Bug 1584564 tracks requiring safe usage of require.js
624 "resource://gre/modules/workers/require.js"_ns
,
626 // The profiler's symbolication code uses a wasm module to extract symbols
627 // from the binary files result of local builds.
629 "resource://devtools/client/performance-new/shared/symbolication.sys.mjs"_ns
,
631 // The Browser Toolbox/Console
635 // We also permit two specific idioms in eval()-like contexts. We'd like to
636 // elminate these too; but there are in-the-wild Mozilla privileged extensions
638 static constexpr auto sAllowedEval1
= u
"this"_ns
;
639 static constexpr auto sAllowedEval2
=
640 u
"function anonymous(\n) {\nreturn this\n}"_ns
;
642 if (MOZ_LIKELY(!aIsSystemPrincipal
&& !XRE_IsE10sParentProcess())) {
643 // We restrict eval in the system principal and parent process.
644 // Other uses (like web content and null principal) are allowed.
648 if (JS::ContextOptionsRef(cx
).disableEvalSecurityChecks()) {
649 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
650 ("Allowing eval() because this JSContext was set to allow it"));
654 if (aIsSystemPrincipal
&&
655 StaticPrefs::security_allow_eval_with_system_principal()) {
656 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
657 ("Allowing eval() with System Principal because allowing pref is "
662 if (XRE_IsE10sParentProcess() &&
663 StaticPrefs::security_allow_eval_in_parent_process()) {
664 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
665 ("Allowing eval() in parent process because allowing pref is "
671 if (MOZ_UNLIKELY(sJSHacksPresent
)) {
673 sCSMLog
, LogLevel::Debug
,
674 ("Allowing eval() %s because some "
675 "JS hacks may be present.",
676 (aIsSystemPrincipal
? "with System Principal" : "in parent process")));
680 if (XRE_IsE10sParentProcess() &&
681 !StaticPrefs::extensions_webextensions_remote()) {
682 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
683 ("Allowing eval() in parent process because the web extension "
684 "process is disabled"));
688 // We permit these two common idioms to get access to the global JS object
689 if (!aScript
.IsEmpty() &&
690 (aScript
== sAllowedEval1
|| aScript
== sAllowedEval2
)) {
692 sCSMLog
, LogLevel::Debug
,
693 ("Allowing eval() %s because a key string is "
695 (aIsSystemPrincipal
? "with System Principal" : "in parent process")));
699 // Check the allowlist for the provided filename. getFilename is a helper
701 auto location
= JSCallingLocation::Get(cx
);
702 const nsCString
& fileName
= location
.FileName();
703 for (const nsLiteralCString
& allowlistEntry
: evalAllowlist
) {
704 // checking if current filename begins with entry, because JS Engine
705 // gives us additional stuff for code inside eval or Function ctor
706 // e.g., "require.js > Function"
707 if (StringBeginsWith(fileName
, allowlistEntry
)) {
708 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
709 ("Allowing eval() %s because the containing "
710 "file is in the allowlist",
711 (aIsSystemPrincipal
? "with System Principal"
712 : "in parent process")));
717 // Send Telemetry and Log to the Console
718 uint64_t windowID
= nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx
);
719 if (NS_IsMainThread()) {
720 nsContentSecurityUtils::NotifyEvalUsage(aIsSystemPrincipal
, fileName
,
721 windowID
, location
.mLine
,
724 auto runnable
= new EvalUsageNotificationRunnable(
725 aIsSystemPrincipal
, fileName
, windowID
, location
.mLine
,
727 NS_DispatchToMainThread(runnable
);
731 MOZ_LOG(sCSMLog
, LogLevel::Error
,
732 ("Blocking eval() %s from file %s and script "
734 (aIsSystemPrincipal
? "with System Principal" : "in parent process"),
735 fileName
.get(), NS_ConvertUTF16toUTF8(aScript
).get()));
738 #if defined(DEBUG) || defined(FUZZING)
739 auto crashString
= nsContentSecurityUtils::SmartFormatCrashString(
740 NS_ConvertUTF16toUTF8(aScript
).get(), fileName
.get(),
742 ? "Blocking eval() with System Principal with script %s from file %s"
743 : "Blocking eval() in parent process with script %s from file %s"));
744 MOZ_CRASH_UNSAFE_PRINTF("%s", crashString
.get());
751 void nsContentSecurityUtils::NotifyEvalUsage(bool aIsSystemPrincipal
,
752 const nsACString
& aFileName
,
754 uint32_t aLineNumber
,
755 uint32_t aColumnNumber
) {
756 FilenameTypeAndDetails fileNameTypeAndDetails
=
757 FilenameToFilenameType(aFileName
, false);
758 auto fileinfo
= fileNameTypeAndDetails
.second
;
759 auto value
= Some(fileNameTypeAndDetails
.first
);
760 if (aIsSystemPrincipal
) {
761 glean::security::EvalUsageSystemContextExtra extra
= {
762 .fileinfo
= fileinfo
,
765 glean::security::eval_usage_system_context
.Record(Some(extra
));
767 glean::security::EvalUsageParentProcessExtra extra
= {
768 .fileinfo
= fileinfo
,
771 glean::security::eval_usage_parent_process
.Record(Some(extra
));
774 // Report an error to console
775 nsCOMPtr
<nsIConsoleService
> console(
776 do_GetService(NS_CONSOLESERVICE_CONTRACTID
));
780 nsCOMPtr
<nsIScriptError
> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
));
784 nsCOMPtr
<nsIStringBundle
> bundle
;
785 nsCOMPtr
<nsIStringBundleService
> stringService
=
786 mozilla::components::StringBundle::Service();
787 if (!stringService
) {
790 stringService
->CreateBundle(
791 "chrome://global/locale/security/security.properties",
792 getter_AddRefs(bundle
));
796 nsAutoString message
;
797 NS_ConvertUTF8toUTF16
fileNameA(aFileName
);
798 AutoTArray
<nsString
, 1> formatStrings
= {fileNameA
};
799 nsresult rv
= bundle
->FormatStringFromName("RestrictBrowserEvalUsage",
800 formatStrings
, message
);
805 rv
= error
->InitWithWindowID(message
, aFileName
, aLineNumber
, aColumnNumber
,
806 nsIScriptError::errorFlag
, "BrowserEvalUsage",
807 aWindowID
, true /* From chrome context */);
811 console
->LogMessage(error
);
814 // If we detect that one of the relevant prefs has been changed, reset
815 // sJSHacksChecked to cause us to re-evaluate all the pref values.
816 // This will stop us from crashing because a user enabled one of these
817 // prefs during a session and then triggered the JavaScript load mitigation
818 // (which can cause a crash).
819 class JSHackPrefObserver final
{
821 JSHackPrefObserver() = default;
822 static void PrefChanged(const char* aPref
, void* aData
);
825 ~JSHackPrefObserver() = default;
829 void JSHackPrefObserver::PrefChanged(const char* aPref
, void* aData
) {
830 sJSHacksChecked
= false;
833 static bool sJSHackObserverAdded
= false;
836 void nsContentSecurityUtils::DetectJsHacks() {
837 // We can only perform the check of this preference on the Main Thread
838 // (because a String-based preference check is only safe on Main Thread.)
839 // In theory, it would be possible that a separate thread could get here
840 // before the main thread, resulting in the other thread not being able to
841 // perform this check, but the odds of that are small (and probably zero.)
842 if (!NS_IsMainThread()) {
846 // If the pref service isn't available, do nothing and re-do this later.
847 if (!Preferences::IsServiceAvailable()) {
851 // No need to check again.
852 if (MOZ_LIKELY(sJSHacksChecked
|| sJSHacksPresent
)) {
856 static const char* kObservedPrefs
[] = {
857 "xpinstall.signatures.required", "general.config.filename",
858 "autoadmin.global_config_url", "autoadmin.failover_to_cached", nullptr};
859 if (MOZ_UNLIKELY(!sJSHackObserverAdded
)) {
860 Preferences::RegisterCallbacks(JSHackPrefObserver::PrefChanged
,
862 sJSHackObserverAdded
= true;
866 sJSHacksChecked
= true;
868 // This preference is required by bootstrapLoader.xpi, which is an
869 // alternate way to load legacy-style extensions. It only works on
870 // DevEdition/Nightly.
871 bool xpinstallSignatures
;
872 rv
= Preferences::GetBool("xpinstall.signatures.required",
873 &xpinstallSignatures
, PrefValueKind::Default
);
874 if (!NS_FAILED(rv
) && !xpinstallSignatures
) {
875 sJSHacksPresent
= true;
878 rv
= Preferences::GetBool("xpinstall.signatures.required",
879 &xpinstallSignatures
, PrefValueKind::User
);
880 if (!NS_FAILED(rv
) && !xpinstallSignatures
) {
881 sJSHacksPresent
= true;
885 if (Preferences::HasDefaultValue("general.config.filename")) {
886 sJSHacksPresent
= true;
889 if (Preferences::HasUserValue("general.config.filename")) {
890 sJSHacksPresent
= true;
893 if (Preferences::HasDefaultValue("autoadmin.global_config_url")) {
894 sJSHacksPresent
= true;
897 if (Preferences::HasUserValue("autoadmin.global_config_url")) {
898 sJSHacksPresent
= true;
902 bool failOverToCache
;
903 rv
= Preferences::GetBool("autoadmin.failover_to_cached", &failOverToCache
,
904 PrefValueKind::Default
);
905 if (!NS_FAILED(rv
) && failOverToCache
) {
906 sJSHacksPresent
= true;
909 rv
= Preferences::GetBool("autoadmin.failover_to_cached", &failOverToCache
,
910 PrefValueKind::User
);
911 if (!NS_FAILED(rv
) && failOverToCache
) {
912 sJSHacksPresent
= true;
917 void nsContentSecurityUtils::DetectCssHacks() {
918 // We can only perform the check of this preference on the Main Thread
919 // It's possible that this function may therefore race and we expect the
920 // caller to ensure that the checks have actually happened.
921 if (!NS_IsMainThread()) {
925 // If the pref service isn't available, do nothing and re-do this later.
926 if (!Preferences::IsServiceAvailable()) {
930 // No need to check again.
931 if (MOZ_LIKELY(sCSSHacksChecked
|| sCSSHacksPresent
)) {
935 // This preference is a bool to see if userChrome css is loaded
936 bool customStylesPresent
= Preferences::GetBool(
937 "toolkit.legacyUserProfileCustomizations.stylesheets", false);
938 if (customStylesPresent
) {
939 sCSSHacksPresent
= true;
942 sCSSHacksChecked
= true;
946 nsresult
nsContentSecurityUtils::GetHttpChannelFromPotentialMultiPart(
947 nsIChannel
* aChannel
, nsIHttpChannel
** aHttpChannel
) {
948 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
950 httpChannel
.forget(aHttpChannel
);
954 nsCOMPtr
<nsIMultiPartChannel
> multipart
= do_QueryInterface(aChannel
);
956 *aHttpChannel
= nullptr;
960 nsCOMPtr
<nsIChannel
> baseChannel
;
961 nsresult rv
= multipart
->GetBaseChannel(getter_AddRefs(baseChannel
));
962 if (NS_WARN_IF(NS_FAILED(rv
))) {
966 httpChannel
= do_QueryInterface(baseChannel
);
967 httpChannel
.forget(aHttpChannel
);
972 nsresult
CheckCSPFrameAncestorPolicy(nsIChannel
* aChannel
,
973 nsIContentSecurityPolicy
** aOutCSP
) {
974 MOZ_ASSERT(aChannel
);
976 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
977 ExtContentPolicyType contentType
= loadInfo
->GetExternalContentPolicyType();
978 // frame-ancestor check only makes sense for subdocument and object loads,
979 // if this is not a load of such type, there is nothing to do here.
980 if (contentType
!= ExtContentPolicy::TYPE_SUBDOCUMENT
&&
981 contentType
!= ExtContentPolicy::TYPE_OBJECT
) {
985 // CSP can only hang off an http channel, if this channel is not
986 // an http channel then there is nothing to do here,
987 // except with add-ons, where the CSP is stored in a WebExtensionPolicy.
988 nsCOMPtr
<nsIHttpChannel
> httpChannel
;
989 nsresult rv
= nsContentSecurityUtils::GetHttpChannelFromPotentialMultiPart(
990 aChannel
, getter_AddRefs(httpChannel
));
991 if (NS_WARN_IF(NS_FAILED(rv
))) {
995 nsAutoCString tCspHeaderValue
, tCspROHeaderValue
;
997 Unused
<< httpChannel
->GetResponseHeader("content-security-policy"_ns
,
1000 Unused
<< httpChannel
->GetResponseHeader(
1001 "content-security-policy-report-only"_ns
, tCspROHeaderValue
);
1003 // if there are no CSP values, then there is nothing to do here.
1004 if (tCspHeaderValue
.IsEmpty() && tCspROHeaderValue
.IsEmpty()) {
1009 nsCOMPtr
<nsIPrincipal
> resultPrincipal
;
1010 rv
= nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
1011 aChannel
, getter_AddRefs(resultPrincipal
));
1012 NS_ENSURE_SUCCESS(rv
, rv
);
1014 RefPtr
<extensions::WebExtensionPolicy
> addonPolicy
;
1016 addonPolicy
= BasePrincipal::Cast(resultPrincipal
)->AddonPolicy();
1018 // Neither a HTTP channel, nor a moz-extension:-resource.
1019 // CSP is not supported.
1024 RefPtr
<nsCSPContext
> csp
= new nsCSPContext();
1025 // This CSPContext is only used for checking frame-ancestors, we
1026 // will parse the CSP again anyway. (Unless this blocks the load, but
1027 // parser warnings aren't really important in that case)
1028 csp
->SuppressParserLogMessages();
1030 nsCOMPtr
<nsIURI
> selfURI
;
1031 nsAutoCString referrerSpec
;
1033 aChannel
->GetURI(getter_AddRefs(selfURI
));
1034 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= httpChannel
->GetReferrerInfo();
1036 referrerInfo
->GetComputedReferrerSpec(referrerSpec
);
1039 // aChannel::GetURI would return the jar: or file:-URI for extensions.
1040 // Use the "final" URI to get the actual moz-extension:-URL.
1041 NS_GetFinalChannelURI(aChannel
, getter_AddRefs(selfURI
));
1044 uint64_t innerWindowID
= loadInfo
->GetInnerWindowID();
1046 rv
= csp
->SetRequestContextWithPrincipal(resultPrincipal
, selfURI
,
1047 referrerSpec
, innerWindowID
);
1048 if (NS_WARN_IF(NS_FAILED(rv
))) {
1053 csp
->AppendPolicy(addonPolicy
->BaseCSP(), false, false);
1054 csp
->AppendPolicy(addonPolicy
->ExtensionPageCSP(), false, false);
1056 NS_ConvertASCIItoUTF16
cspHeaderValue(tCspHeaderValue
);
1057 NS_ConvertASCIItoUTF16
cspROHeaderValue(tCspROHeaderValue
);
1059 // ----- if there's a full-strength CSP header, apply it.
1060 if (!cspHeaderValue
.IsEmpty()) {
1061 rv
= CSP_AppendCSPFromHeader(csp
, cspHeaderValue
, false);
1062 NS_ENSURE_SUCCESS(rv
, rv
);
1065 // ----- if there's a report-only CSP header, apply it.
1066 if (!cspROHeaderValue
.IsEmpty()) {
1067 rv
= CSP_AppendCSPFromHeader(csp
, cspROHeaderValue
, true);
1068 NS_ENSURE_SUCCESS(rv
, rv
);
1072 // ----- Enforce frame-ancestor policy on any applied policies
1073 bool safeAncestry
= false;
1074 // PermitsAncestry sends violation reports when necessary
1075 rv
= csp
->PermitsAncestry(loadInfo
, &safeAncestry
);
1077 if (NS_FAILED(rv
) || !safeAncestry
) {
1078 // stop! ERROR page!
1079 return NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION
;
1082 // return the CSP for x-frame-options check
1083 csp
.forget(aOutCSP
);
1088 void EnforceCSPFrameAncestorPolicy(nsIChannel
* aChannel
,
1089 const nsresult
& aError
) {
1090 if (aError
== NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION
) {
1091 aChannel
->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION
);
1095 void EnforceXFrameOptionsCheck(nsIChannel
* aChannel
,
1096 nsIContentSecurityPolicy
* aCsp
) {
1097 MOZ_ASSERT(aChannel
);
1098 bool isFrameOptionsIgnored
= false;
1099 // check for XFO options
1100 // XFO checks can be skipped if there are frame ancestors
1101 if (!FramingChecker::CheckFrameOptions(aChannel
, aCsp
,
1102 isFrameOptionsIgnored
)) {
1103 // stop! ERROR page!
1104 aChannel
->Cancel(NS_ERROR_XFO_VIOLATION
);
1107 if (isFrameOptionsIgnored
) {
1108 // log warning to console that xfo is ignored because of CSP
1109 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1110 uint64_t innerWindowID
= loadInfo
->GetInnerWindowID();
1111 bool privateWindow
= loadInfo
->GetOriginAttributes().IsPrivateBrowsing();
1112 AutoTArray
<nsString
, 2> params
= {u
"x-frame-options"_ns
,
1113 u
"frame-ancestors"_ns
};
1114 CSP_LogLocalizedStr("IgnoringSrcBecauseOfDirective", params
,
1115 ""_ns
, // no sourcefile
1116 u
""_ns
, // no scriptsample
1118 1, // no columnnumber
1119 nsIScriptError::warningFlag
,
1120 "IgnoringSrcBecauseOfDirective"_ns
, innerWindowID
,
1126 void nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(
1127 nsIChannel
* aChannel
) {
1128 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
1129 nsresult rv
= CheckCSPFrameAncestorPolicy(aChannel
, getter_AddRefs(csp
));
1131 if (NS_FAILED(rv
)) {
1132 EnforceCSPFrameAncestorPolicy(aChannel
, rv
);
1136 // X-Frame-Options needs to be enforced after CSP frame-ancestors
1137 // checks because if frame-ancestors is present, then x-frame-options
1138 // will be discarded
1139 EnforceXFrameOptionsCheck(aChannel
, csp
);
1142 bool nsContentSecurityUtils::CheckCSPFrameAncestorAndXFO(nsIChannel
* aChannel
) {
1143 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
1144 nsresult rv
= CheckCSPFrameAncestorPolicy(aChannel
, getter_AddRefs(csp
));
1146 if (NS_FAILED(rv
)) {
1150 bool isFrameOptionsIgnored
= false;
1152 return FramingChecker::CheckFrameOptions(aChannel
, csp
,
1153 isFrameOptionsIgnored
);
1156 // https://w3c.github.io/webappsec-csp/#is-element-nonceable
1158 nsString
nsContentSecurityUtils::GetIsElementNonceableNonce(
1159 const Element
& aElement
) {
1160 // Step 1. If element does not have an attribute named "nonce", return "Not
1163 if (nsString
* cspNonce
=
1164 static_cast<nsString
*>(aElement
.GetProperty(nsGkAtoms::nonce
))) {
1167 if (nonce
.IsEmpty()) {
1171 // Step 2. If element is a script element, then for each attribute of
1172 // element’s attribute list:
1173 if (nsCOMPtr
<nsIScriptElement
> script
=
1174 do_QueryInterface(const_cast<Element
*>(&aElement
))) {
1175 auto containsScriptOrStyle
= [](const nsAString
& aStr
) {
1176 return aStr
.LowerCaseFindASCII("<script") != kNotFound
||
1177 aStr
.LowerCaseFindASCII("<style") != kNotFound
;
1182 while (BorrowedAttrInfo info
= aElement
.GetAttrInfoAt(i
++)) {
1183 // Step 2.1. If attribute’s name contains an ASCII case-insensitive match
1184 // for "<script" or "<style", return "Not Nonceable".
1185 const nsAttrName
* name
= info
.mName
;
1186 if (nsAtom
* prefix
= name
->GetPrefix()) {
1187 if (containsScriptOrStyle(nsDependentAtomString(prefix
))) {
1188 return EmptyString();
1191 if (containsScriptOrStyle(nsDependentAtomString(name
->LocalName()))) {
1192 return EmptyString();
1195 // Step 2.2. If attribute’s value contains an ASCII case-insensitive match
1196 // for "<script" or "<style", return "Not Nonceable".
1197 info
.mValue
->ToString(value
);
1198 if (containsScriptOrStyle(value
)) {
1199 return EmptyString();
1204 // Step 3. If element had a duplicate-attribute parse error during
1205 // tokenization, return "Not Nonceable".
1206 if (aElement
.HasFlag(ELEMENT_PARSER_HAD_DUPLICATE_ATTR_ERROR
)) {
1207 return EmptyString();
1210 // Step 4. Return "Nonceable".
1216 void nsContentSecurityUtils::AssertAboutPageHasCSP(Document
* aDocument
) {
1217 // We want to get to a point where all about: pages ship with a CSP. This
1218 // assertion ensures that we can not deploy new about: pages without a CSP.
1219 // Please note that any about: page should not use inline JS or inline CSS,
1220 // and instead should load JS and CSS from an external file (*.js, *.css)
1221 // which allows us to apply a strong CSP omitting 'unsafe-inline'. Ideally,
1222 // the CSP allows precisely the resources that need to be loaded; but it
1223 // should at least be as strong as:
1224 // <meta http-equiv="Content-Security-Policy" content="default-src chrome:;
1225 // object-src 'none'"/>
1227 // This is a data document, created using DOMParser or
1228 // document.implementation.createDocument() or such, not an about: page which
1229 // is loaded as a web page.
1230 if (aDocument
->IsLoadedAsData()) {
1234 // Check if we should skip the assertion
1235 if (StaticPrefs::dom_security_skip_about_page_has_csp_assert()) {
1239 // Check if we are loading an about: URI at all
1240 nsCOMPtr
<nsIURI
> documentURI
= aDocument
->GetDocumentURI();
1241 if (!documentURI
->SchemeIs("about")) {
1245 nsCOMPtr
<nsIContentSecurityPolicy
> csp
= aDocument
->GetCsp();
1246 bool foundDefaultSrc
= false;
1247 bool foundObjectSrc
= false;
1248 bool foundUnsafeEval
= false;
1249 bool foundUnsafeInline
= false;
1250 bool foundScriptSrc
= false;
1251 bool foundWorkerSrc
= false;
1252 bool foundWebScheme
= false;
1254 uint32_t policyCount
= 0;
1255 csp
->GetPolicyCount(&policyCount
);
1256 nsAutoString parsedPolicyStr
;
1257 for (uint32_t i
= 0; i
< policyCount
; ++i
) {
1258 csp
->GetPolicyString(i
, parsedPolicyStr
);
1259 if (parsedPolicyStr
.Find(u
"default-src") >= 0) {
1260 foundDefaultSrc
= true;
1262 if (parsedPolicyStr
.Find(u
"object-src 'none'") >= 0) {
1263 foundObjectSrc
= true;
1265 if (parsedPolicyStr
.Find(u
"'unsafe-eval'") >= 0) {
1266 foundUnsafeEval
= true;
1268 if (parsedPolicyStr
.Find(u
"'unsafe-inline'") >= 0) {
1269 foundUnsafeInline
= true;
1271 if (parsedPolicyStr
.Find(u
"script-src") >= 0) {
1272 foundScriptSrc
= true;
1274 if (parsedPolicyStr
.Find(u
"worker-src") >= 0) {
1275 foundWorkerSrc
= true;
1277 if (parsedPolicyStr
.Find(u
"http:") >= 0 ||
1278 parsedPolicyStr
.Find(u
"https:") >= 0) {
1279 foundWebScheme
= true;
1284 // Check if we should skip the allowlist and assert right away. Please note
1285 // that this pref can and should only be set for automated testing.
1286 if (StaticPrefs::dom_security_skip_about_page_csp_allowlist_and_assert()) {
1287 NS_ASSERTION(foundDefaultSrc
, "about: page must have a CSP");
1291 nsAutoCString aboutSpec
;
1292 documentURI
->GetSpec(aboutSpec
);
1293 ToLowerCase(aboutSpec
);
1295 // This allowlist contains about: pages that are permanently allowed to
1296 // render without a CSP applied.
1297 static nsLiteralCString sAllowedAboutPagesWithNoCSP
[] = {
1298 // about:blank is a special about page -> no CSP
1300 // about:srcdoc is a special about page -> no CSP
1302 // about:sync-log displays plain text only -> no CSP
1303 "about:sync-log"_ns
,
1304 // about:logo just displays the firefox logo -> no CSP
1306 // about:sync is a special mozilla-signed developer addon with low usage
1310 # if defined(ANDROID)
1315 for (const nsLiteralCString
& allowlistEntry
: sAllowedAboutPagesWithNoCSP
) {
1316 // please note that we perform a substring match here on purpose,
1317 // so we don't have to deal and parse out all the query arguments
1318 // the various about pages rely on.
1319 if (StringBeginsWith(aboutSpec
, allowlistEntry
)) {
1324 MOZ_ASSERT(foundDefaultSrc
,
1325 "about: page must contain a CSP including default-src");
1326 MOZ_ASSERT(foundObjectSrc
,
1327 "about: page must contain a CSP denying object-src");
1329 // preferences and downloads allow legacy inline scripts through hash src.
1331 !foundScriptSrc
|| StringBeginsWith(aboutSpec
, "about:preferences"_ns
) ||
1332 StringBeginsWith(aboutSpec
, "about:settings"_ns
) ||
1333 StringBeginsWith(aboutSpec
, "about:downloads"_ns
) ||
1334 StringBeginsWith(aboutSpec
, "about:fingerprintingprotection"_ns
) ||
1335 StringBeginsWith(aboutSpec
, "about:asrouter"_ns
) ||
1336 StringBeginsWith(aboutSpec
, "about:newtab"_ns
) ||
1337 StringBeginsWith(aboutSpec
, "about:logins"_ns
) ||
1338 StringBeginsWith(aboutSpec
, "about:compat"_ns
) ||
1339 StringBeginsWith(aboutSpec
, "about:welcome"_ns
) ||
1340 StringBeginsWith(aboutSpec
, "about:profiling"_ns
) ||
1341 StringBeginsWith(aboutSpec
, "about:studies"_ns
) ||
1342 StringBeginsWith(aboutSpec
, "about:home"_ns
),
1343 "about: page must not contain a CSP including script-src");
1345 MOZ_ASSERT(!foundWorkerSrc
,
1346 "about: page must not contain a CSP including worker-src");
1348 // addons, preferences, debugging, ion, devtools all have to allow some
1349 // remote web resources
1350 MOZ_ASSERT(!foundWebScheme
||
1351 StringBeginsWith(aboutSpec
, "about:preferences"_ns
) ||
1352 StringBeginsWith(aboutSpec
, "about:settings"_ns
) ||
1353 StringBeginsWith(aboutSpec
, "about:addons"_ns
) ||
1354 StringBeginsWith(aboutSpec
, "about:newtab"_ns
) ||
1355 StringBeginsWith(aboutSpec
, "about:debugging"_ns
) ||
1356 StringBeginsWith(aboutSpec
, "about:ion"_ns
) ||
1357 StringBeginsWith(aboutSpec
, "about:compat"_ns
) ||
1358 StringBeginsWith(aboutSpec
, "about:logins"_ns
) ||
1359 StringBeginsWith(aboutSpec
, "about:home"_ns
) ||
1360 StringBeginsWith(aboutSpec
, "about:welcome"_ns
) ||
1361 StringBeginsWith(aboutSpec
, "about:devtools"_ns
) ||
1362 StringBeginsWith(aboutSpec
, "about:pocket-saved"_ns
) ||
1363 StringBeginsWith(aboutSpec
, "about:pocket-home"_ns
),
1364 "about: page must not contain a CSP including a web scheme");
1366 if (aDocument
->IsExtensionPage()) {
1367 // Extensions have two CSP policies applied where the baseline CSP
1368 // includes 'unsafe-eval' and 'unsafe-inline', hence we have to skip
1369 // the 'unsafe-eval' and 'unsafe-inline' assertions for extension
1374 MOZ_ASSERT(!foundUnsafeEval
,
1375 "about: page must not contain a CSP including 'unsafe-eval'");
1377 static nsLiteralCString sLegacyUnsafeInlineAllowList
[] = {
1378 // Bug 1579160: Remove 'unsafe-inline' from style-src within
1379 // about:preferences
1380 "about:preferences"_ns
,
1381 "about:settings"_ns
,
1382 // Bug 1571346: Remove 'unsafe-inline' from style-src within about:addons
1384 // Bug 1584485: Remove 'unsafe-inline' from style-src within:
1393 for (const nsLiteralCString
& aUnsafeInlineEntry
:
1394 sLegacyUnsafeInlineAllowList
) {
1395 // please note that we perform a substring match here on purpose,
1396 // so we don't have to deal and parse out all the query arguments
1397 // the various about pages rely on.
1398 if (StringBeginsWith(aboutSpec
, aUnsafeInlineEntry
)) {
1403 MOZ_ASSERT(!foundUnsafeInline
,
1404 "about: page must not contain a CSP including 'unsafe-inline'");
1409 bool nsContentSecurityUtils::ValidateScriptFilename(JSContext
* cx
,
1410 const char* aFilename
) {
1411 // If the pref is permissive, allow everything
1412 if (StaticPrefs::security_allow_parent_unrestricted_js_loads()) {
1416 // If we're not in the parent process allow everything (presently)
1417 if (!XRE_IsE10sParentProcess()) {
1421 // If we have allowed eval (because of a user configuration or more
1422 // likely a test has requested it), and the script is an eval, allow it.
1423 nsDependentCString
filename(aFilename
);
1424 if (StaticPrefs::security_allow_eval_with_system_principal() ||
1425 StaticPrefs::security_allow_eval_in_parent_process()) {
1426 if (StringEndsWith(filename
, "> eval"_ns
)) {
1433 if (MOZ_UNLIKELY(!sJSHacksChecked
)) {
1435 sCSMLog
, LogLevel::Debug
,
1436 ("Allowing a javascript load of %s because "
1437 "we have not yet been able to determine if JS hacks may be present",
1442 if (MOZ_UNLIKELY(sJSHacksPresent
)) {
1443 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1444 ("Allowing a javascript load of %s because "
1445 "some JS hacks may be present",
1450 if (XRE_IsE10sParentProcess() &&
1451 !StaticPrefs::extensions_webextensions_remote()) {
1452 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1453 ("Allowing a javascript load of %s because the web extension "
1454 "process is disabled.",
1459 if (StringBeginsWith(filename
, "chrome://"_ns
)) {
1460 // If it's a chrome:// url, allow it
1463 if (StringBeginsWith(filename
, "resource://"_ns
)) {
1464 // If it's a resource:// url, allow it
1467 if (StringBeginsWith(filename
, "file://"_ns
)) {
1468 // We will temporarily allow all file:// URIs through for now
1471 if (StringBeginsWith(filename
, "jar:file://"_ns
)) {
1472 // We will temporarily allow all jar URIs through for now
1475 if (filename
.Equals("about:sync-log"_ns
)) {
1476 // about:sync-log runs in the parent process and displays a directory
1477 // listing. The listing has inline javascript that executes on load.
1481 if (StringBeginsWith(filename
, "moz-extension://"_ns
)) {
1482 nsCOMPtr
<nsIURI
> uri
;
1483 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aFilename
);
1484 if (!NS_FAILED(rv
) && NS_IsMainThread()) {
1485 mozilla::extensions::URLInfo
url(uri
);
1487 ExtensionPolicyService::GetSingleton().GetByHost(url
.Host());
1489 if (policy
&& policy
->IsPrivileged()) {
1490 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1491 ("Allowing a javascript load of %s because the web extension "
1492 "it is associated with is privileged.",
1497 } else if (!NS_IsMainThread()) {
1498 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
1499 if (workerPrivate
&& workerPrivate
->IsPrivilegedAddonGlobal()) {
1500 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1501 ("Allowing a javascript load of %s because the web extension "
1502 "it is associated with is privileged.",
1508 auto kAllowedFilenamesPrefix
= {
1509 // Until 371900 is fixed, we need to do something about about:downloads
1510 // and this is the most reasonable. See 1727770
1511 "about:downloads"_ns
,
1512 // We think this is the same problem as about:downloads
1513 "about:preferences"_ns
, "about:settings"_ns
,
1514 // Browser console will give a filename of 'debugger' See 1763943
1515 // Sometimes it's 'debugger eager eval code', other times just 'debugger
1519 for (auto allowedFilenamePrefix
: kAllowedFilenamesPrefix
) {
1520 if (StringBeginsWith(filename
, allowedFilenamePrefix
)) {
1526 MOZ_LOG(sCSMLog
, LogLevel::Error
,
1527 ("ValidateScriptFilename Failed: %s\n", aFilename
));
1529 FilenameTypeAndDetails fileNameTypeAndDetails
=
1530 FilenameToFilenameType(filename
, true);
1532 glean::security::JavascriptLoadParentProcessExtra extra
= {
1533 .fileinfo
= fileNameTypeAndDetails
.second
,
1534 .value
= Some(fileNameTypeAndDetails
.first
),
1536 glean::security::javascript_load_parent_process
.Record(Some(extra
));
1538 #if defined(DEBUG) || defined(FUZZING)
1539 auto crashString
= nsContentSecurityUtils::SmartFormatCrashString(
1541 fileNameTypeAndDetails
.second
.isSome()
1542 ? fileNameTypeAndDetails
.second
.value().get()
1544 "Blocking a script load %s from file %s");
1545 MOZ_CRASH_UNSAFE_PRINTF("%s", crashString
.get());
1546 #elif defined(EARLY_BETA_OR_EARLIER)
1547 // Cause a crash (if we've never crashed before and we can ensure we won't do
1549 // The details in the second arg, passed to UNSAFE_PRINTF, are also included
1550 // in Event Telemetry and have received data review.
1551 if (fileNameTypeAndDetails
.second
.isSome()) {
1552 PossiblyCrash("js_load_1", aFilename
,
1553 fileNameTypeAndDetails
.second
.value());
1555 PossiblyCrash("js_load_1", aFilename
, "(None)"_ns
);
1559 // Presently we are only enforcing restrictions for the script filename
1560 // on Nightly. On all channels we are reporting Telemetry. In the future we
1561 // will assert in debug builds and return false to prevent execution in
1562 // non-debug builds.
1563 #ifdef NIGHTLY_BUILD
1571 void nsContentSecurityUtils::LogMessageToConsole(nsIHttpChannel
* aChannel
,
1573 nsCOMPtr
<nsIURI
> uri
;
1574 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1575 if (NS_FAILED(rv
)) {
1579 uint64_t windowID
= 0;
1580 rv
= aChannel
->GetTopLevelContentWindowId(&windowID
);
1581 if (NS_WARN_IF(NS_FAILED(rv
))) {
1585 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1586 loadInfo
->GetInnerWindowID(&windowID
);
1589 nsAutoString localizedMsg
;
1592 AutoTArray
<nsString
, 1> params
= {NS_ConvertUTF8toUTF16(spec
)};
1593 rv
= nsContentUtils::FormatLocalizedString(
1594 nsContentUtils::eSECURITY_PROPERTIES
, aMsg
, params
, localizedMsg
);
1595 if (NS_WARN_IF(NS_FAILED(rv
))) {
1599 nsContentUtils::ReportToConsoleByWindowID(
1600 localizedMsg
, nsIScriptError::warningFlag
, "Security"_ns
, windowID
,
1601 SourceLocation
{uri
.get()});
1605 long nsContentSecurityUtils::ClassifyDownload(
1606 nsIChannel
* aChannel
, const nsAutoCString
& aMimeTypeGuess
) {
1607 MOZ_ASSERT(aChannel
, "IsDownloadAllowed without channel?");
1609 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1611 nsCOMPtr
<nsIURI
> contentLocation
;
1612 aChannel
->GetURI(getter_AddRefs(contentLocation
));
1614 nsCOMPtr
<nsIPrincipal
> loadingPrincipal
= loadInfo
->GetLoadingPrincipal();
1615 if (!loadingPrincipal
) {
1616 loadingPrincipal
= loadInfo
->TriggeringPrincipal();
1618 // Creating a fake Loadinfo that is just used for the MCB check.
1619 nsCOMPtr
<nsILoadInfo
> secCheckLoadInfo
= new mozilla::net::LoadInfo(
1620 loadingPrincipal
, loadInfo
->TriggeringPrincipal(), nullptr,
1621 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
,
1622 nsIContentPolicy::TYPE_FETCH
);
1623 // Disable HTTPS-Only checks for that loadinfo. This is required because
1624 // otherwise nsMixedContentBlocker::ShouldLoad would assume that the request
1625 // is safe, because HTTPS-Only is handling it.
1626 secCheckLoadInfo
->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT
);
1628 int16_t decission
= nsIContentPolicy::ACCEPT
;
1629 nsMixedContentBlocker::ShouldLoad(false, // aHadInsecureImageRedirect
1630 contentLocation
, // aContentLocation,
1631 secCheckLoadInfo
, // aLoadinfo
1632 false, // aReportError
1633 &decission
// aDecision
1636 if (StaticPrefs::dom_block_download_insecure() &&
1637 decission
!= nsIContentPolicy::ACCEPT
) {
1638 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
1640 LogMessageToConsole(httpChannel
, "MixedContentBlockedDownload");
1642 return nsITransfer::DOWNLOAD_POTENTIALLY_UNSAFE
;
1645 if (loadInfo
->TriggeringPrincipal()->IsSystemPrincipal()) {
1646 return nsITransfer::DOWNLOAD_ACCEPTABLE
;
1649 uint32_t triggeringFlags
= loadInfo
->GetTriggeringSandboxFlags();
1650 uint32_t currentflags
= loadInfo
->GetSandboxFlags();
1652 if ((triggeringFlags
& SANDBOXED_ALLOW_DOWNLOADS
) ||
1653 (currentflags
& SANDBOXED_ALLOW_DOWNLOADS
)) {
1654 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
1656 LogMessageToConsole(httpChannel
, "IframeSandboxBlockedDownload");
1658 return nsITransfer::DOWNLOAD_FORBIDDEN
;
1660 return nsITransfer::DOWNLOAD_ACCEPTABLE
;