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/StaticPrefs_security.h"
45 #include "mozilla/StaticPrefs_extensions.h"
46 #include "mozilla/StaticPrefs_dom.h"
47 #include "mozilla/Telemetry.h"
48 #include "mozilla/TelemetryComms.h"
49 #include "mozilla/TelemetryEventEnums.h"
50 #include "nsIConsoleService.h"
51 #include "nsIStringBundle.h"
53 using namespace mozilla
;
54 using namespace mozilla::dom
;
55 using namespace mozilla::Telemetry
;
57 extern mozilla::LazyLogModule sCSMLog
;
58 extern Atomic
<bool, mozilla::Relaxed
> sJSHacksChecked
;
59 extern Atomic
<bool, mozilla::Relaxed
> sJSHacksPresent
;
60 extern Atomic
<bool, mozilla::Relaxed
> sCSSHacksChecked
;
61 extern Atomic
<bool, mozilla::Relaxed
> sCSSHacksPresent
;
62 extern Atomic
<bool, mozilla::Relaxed
> sTelemetryEventEnabled
;
64 // Helper function for IsConsideredSameOriginForUIR which makes
65 // Principals of scheme 'http' return Principals of scheme 'https'.
66 static already_AddRefed
<nsIPrincipal
> MakeHTTPPrincipalHTTPS(
67 nsIPrincipal
* aPrincipal
) {
68 nsCOMPtr
<nsIPrincipal
> principal
= aPrincipal
;
69 // if the principal is not http, then it can also not be upgraded
71 if (!principal
->SchemeIs("http")) {
72 return principal
.forget();
76 aPrincipal
->GetAsciiSpec(spec
);
77 // replace http with https
78 spec
.ReplaceLiteral(0, 4, "https");
80 nsCOMPtr
<nsIURI
> newURI
;
81 nsresult rv
= NS_NewURI(getter_AddRefs(newURI
), spec
);
82 if (NS_WARN_IF(NS_FAILED(rv
))) {
86 mozilla::OriginAttributes OA
=
87 BasePrincipal::Cast(aPrincipal
)->OriginAttributesRef();
89 principal
= BasePrincipal::CreateContentPrincipal(newURI
, OA
);
90 return principal
.forget();
94 bool nsContentSecurityUtils::IsConsideredSameOriginForUIR(
95 nsIPrincipal
* aTriggeringPrincipal
, nsIPrincipal
* aResultPrincipal
) {
96 MOZ_ASSERT(aTriggeringPrincipal
);
97 MOZ_ASSERT(aResultPrincipal
);
98 // we only have to make sure that the following truth table holds:
99 // aTriggeringPrincipal | aResultPrincipal | Result
100 // ----------------------------------------------------------------
101 // http://example.com/foo.html | http://example.com/bar.html | true
102 // http://example.com/foo.html | https://example.com/bar.html | true
103 // https://example.com/foo.html | https://example.com/bar.html | true
104 // https://example.com/foo.html | http://example.com/bar.html | true
106 // fast path if both principals are same-origin
107 if (aTriggeringPrincipal
->Equals(aResultPrincipal
)) {
111 // in case a principal uses a scheme of 'http' then we just upgrade to
112 // 'https' and use the principal equals comparison operator to check
114 nsCOMPtr
<nsIPrincipal
> compareTriggeringPrincipal
=
115 MakeHTTPPrincipalHTTPS(aTriggeringPrincipal
);
117 nsCOMPtr
<nsIPrincipal
> compareResultPrincipal
=
118 MakeHTTPPrincipalHTTPS(aResultPrincipal
);
120 return compareTriggeringPrincipal
->Equals(compareResultPrincipal
);
124 * Performs a Regular Expression match, optionally returning the results.
125 * This function is not safe to use OMT.
127 * @param aPattern The regex pattern
128 * @param aString The string to compare against
129 * @param aOnlyMatch Whether we want match results or only a true/false for
131 * @param aMatchResult Out param for whether or not the pattern matched
132 * @param aRegexResults Out param for the matches of the regex, if requested
133 * @returns nsresult indicating correct function operation or error
135 nsresult
RegexEval(const nsAString
& aPattern
, const nsAString
& aString
,
136 bool aOnlyMatch
, bool& aMatchResult
,
137 nsTArray
<nsString
>* aRegexResults
= nullptr) {
138 MOZ_ASSERT(NS_IsMainThread());
139 aMatchResult
= false;
141 mozilla::dom::AutoJSAPI jsapi
;
144 JSContext
* cx
= jsapi
.cx();
145 mozilla::AutoDisableJSInterruptCallback
disabler(cx
);
147 // We can use the junk scope here, because we're just using it for regexp
148 // evaluation, not actual script execution, and we disable statics so that the
149 // evaluation does not interact with the execution global.
150 JSAutoRealm
ar(cx
, xpc::PrivilegedJunkScope());
152 JS::Rooted
<JSObject
*> regexp(
153 cx
, JS::NewUCRegExpObject(cx
, aPattern
.BeginReading(), aPattern
.Length(),
154 JS::RegExpFlag::Unicode
));
156 return NS_ERROR_ILLEGAL_VALUE
;
159 JS::Rooted
<JS::Value
> regexResult(cx
, JS::NullValue());
162 if (!JS::ExecuteRegExpNoStatics(cx
, regexp
, aString
.BeginReading(),
163 aString
.Length(), &index
, aOnlyMatch
,
165 return NS_ERROR_FAILURE
;
168 if (regexResult
.isNull()) {
169 // On no match, ExecuteRegExpNoStatics returns Null
173 // On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean
175 MOZ_ASSERT(regexResult
.isBoolean() && regexResult
.toBoolean());
179 if (aRegexResults
== nullptr) {
180 return NS_ERROR_INVALID_ARG
;
183 // Now we know we have a result, and we need to extract it so we can read it.
185 JS::Rooted
<JSObject
*> regexResultObj(cx
, ®exResult
.toObject());
186 if (!JS::GetArrayLength(cx
, regexResultObj
, &length
)) {
187 return NS_ERROR_NOT_AVAILABLE
;
189 MOZ_LOG(sCSMLog
, LogLevel::Verbose
, ("Regex Matched %i strings", length
));
191 for (uint32_t i
= 0; i
< length
; i
++) {
192 JS::Rooted
<JS::Value
> element(cx
);
193 if (!JS_GetElement(cx
, regexResultObj
, i
, &element
)) {
194 return NS_ERROR_NO_CONTENT
;
197 nsAutoJSString value
;
198 if (!value
.init(cx
, element
)) {
199 return NS_ERROR_NO_CONTENT
;
202 MOZ_LOG(sCSMLog
, LogLevel::Verbose
,
203 ("Regex Matching: %i: %s", i
, NS_ConvertUTF16toUTF8(value
).get()));
204 aRegexResults
->AppendElement(value
);
212 * MOZ_CRASH_UNSAFE_PRINTF has a sPrintfCrashReasonSize-sized buffer. We need
213 * to make sure we don't exceed it. These functions perform this check and
214 * munge things for us.
219 * Destructively truncates a string to fit within the limit
221 char* nsContentSecurityUtils::SmartFormatCrashString(const char* str
) {
222 return nsContentSecurityUtils::SmartFormatCrashString(strdup(str
));
225 char* nsContentSecurityUtils::SmartFormatCrashString(char* str
) {
226 auto str_len
= strlen(str
);
228 if (str_len
> sPrintfCrashReasonSize
) {
229 str
[sPrintfCrashReasonSize
- 1] = '\0';
230 str_len
= strlen(str
);
232 MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize
> str_len
);
238 * Destructively truncates two strings to fit within the limit.
239 * format_string is a format string containing two %s entries
240 * The second string will be truncated to the _last_ 25 characters
241 * The first string will be truncated to the remaining limit.
243 nsCString
nsContentSecurityUtils::SmartFormatCrashString(
244 const char* part1
, const char* part2
, const char* format_string
) {
245 return SmartFormatCrashString(strdup(part1
), strdup(part2
), format_string
);
248 nsCString
nsContentSecurityUtils::SmartFormatCrashString(
249 char* part1
, char* part2
, const char* format_string
) {
250 auto part1_len
= strlen(part1
);
251 auto part2_len
= strlen(part2
);
253 auto constant_len
= strlen(format_string
) - 4;
255 if (part1_len
+ part2_len
+ constant_len
> sPrintfCrashReasonSize
) {
256 if (part2_len
> 25) {
257 part2
+= (part2_len
- 25);
259 part2_len
= strlen(part2
);
261 part1
[sPrintfCrashReasonSize
- (constant_len
+ part2_len
+ 1)] = '\0';
262 part1_len
= strlen(part1
);
264 MOZ_RELEASE_ASSERT(sPrintfCrashReasonSize
>
265 constant_len
+ part1_len
+ part2_len
);
267 auto parts
= nsPrintfCString(format_string
, part1
, part2
);
268 return std::move(parts
);
272 * Telemetry Events extra data only supports 80 characters, so we optimize the
273 * filename to be smaller and collect more data.
275 nsString
OptimizeFileName(const nsAString
& aFileName
) {
276 nsString
optimizedName(aFileName
);
279 sCSMLog
, LogLevel::Verbose
,
280 ("Optimizing FileName: %s", NS_ConvertUTF16toUTF8(optimizedName
).get()));
282 optimizedName
.ReplaceSubstring(u
".xpi!"_ns
, u
"!"_ns
);
283 optimizedName
.ReplaceSubstring(u
"shield.mozilla.org!"_ns
, u
"s!"_ns
);
284 optimizedName
.ReplaceSubstring(u
"mozilla.org!"_ns
, u
"m!"_ns
);
285 if (optimizedName
.Length() > 80) {
286 optimizedName
.Truncate(80);
290 sCSMLog
, LogLevel::Verbose
,
291 ("Optimized FileName: %s", NS_ConvertUTF16toUTF8(optimizedName
).get()));
292 return optimizedName
;
296 * FilenameToFilenameType takes a fileName and returns a Pair of strings.
297 * The First entry is a string indicating the type of fileName
298 * The Second entry is a Maybe<string> that can contain additional details to
301 * The reason we use strings (instead of an int/enum) is because the Telemetry
302 * Events API only accepts strings.
304 * Function is a static member of the class to enable gtests.
308 FilenameTypeAndDetails
nsContentSecurityUtils::FilenameToFilenameType(
309 const nsString
& fileName
, bool collectAdditionalExtensionData
) {
310 // These are strings because the Telemetry Events API only accepts strings
311 static constexpr auto kChromeURI
= "chromeuri"_ns
;
312 static constexpr auto kResourceURI
= "resourceuri"_ns
;
313 static constexpr auto kBlobUri
= "bloburi"_ns
;
314 static constexpr auto kDataUri
= "dataurl"_ns
;
315 static constexpr auto kAboutUri
= "abouturi"_ns
;
316 static constexpr auto kDataUriWebExtCStyle
=
317 "dataurl-extension-contentstyle"_ns
;
318 static constexpr auto kSingleString
= "singlestring"_ns
;
319 static constexpr auto kMozillaExtensionFile
= "mozillaextension_file"_ns
;
320 static constexpr auto kOtherExtensionFile
= "otherextension_file"_ns
;
321 static constexpr auto kExtensionURI
= "extension_uri"_ns
;
322 static constexpr auto kSuspectedUserChromeJS
= "suspectedUserChromeJS"_ns
;
324 static constexpr auto kSanitizedWindowsURL
= "sanitizedWindowsURL"_ns
;
325 static constexpr auto kSanitizedWindowsPath
= "sanitizedWindowsPath"_ns
;
327 static constexpr auto kOther
= "other"_ns
;
328 static constexpr auto kOtherWorker
= "other-on-worker"_ns
;
329 static constexpr auto kRegexFailure
= "regexfailure"_ns
;
331 static constexpr auto kUCJSRegex
= u
"(.+).uc.js\\?*[0-9]*$"_ns
;
332 static constexpr auto kExtensionRegex
= u
"extensions/(.+)@(.+)!(.+)$"_ns
;
333 static constexpr auto kSingleFileRegex
= u
"^[a-zA-Z0-9.?]+$"_ns
;
335 if (fileName
.IsEmpty()) {
336 return FilenameTypeAndDetails(kOther
, Nothing());
339 // resource:// and chrome://
340 if (StringBeginsWith(fileName
, u
"chrome://"_ns
)) {
341 return FilenameTypeAndDetails(kChromeURI
, Some(fileName
));
343 if (StringBeginsWith(fileName
, u
"resource://"_ns
)) {
344 return FilenameTypeAndDetails(kResourceURI
, Some(fileName
));
348 if (StringBeginsWith(fileName
, u
"blob:"_ns
)) {
349 return FilenameTypeAndDetails(kBlobUri
, Nothing());
351 if (StringBeginsWith(fileName
, u
"data:text/css;extension=style;"_ns
)) {
352 return FilenameTypeAndDetails(kDataUriWebExtCStyle
, Nothing());
354 if (StringBeginsWith(fileName
, u
"data:"_ns
)) {
355 return FilenameTypeAndDetails(kDataUri
, Nothing());
358 // Can't do regex matching off-main-thread
359 if (NS_IsMainThread()) {
360 // Extension as loaded via a file://
362 nsTArray
<nsString
> regexResults
;
363 nsresult rv
= RegexEval(kExtensionRegex
, fileName
, /* aOnlyMatch = */ false,
364 regexMatch
, ®exResults
);
366 return FilenameTypeAndDetails(kRegexFailure
, Nothing());
369 nsCString type
= StringEndsWith(regexResults
[2], u
"mozilla.org.xpi"_ns
)
370 ? kMozillaExtensionFile
371 : kOtherExtensionFile
;
372 const auto& extensionNameAndPath
=
373 Substring(regexResults
[0], ArrayLength("extensions/") - 1);
374 return FilenameTypeAndDetails(
375 type
, Some(OptimizeFileName(extensionNameAndPath
)));
379 rv
= RegexEval(kSingleFileRegex
, fileName
, /* aOnlyMatch = */ true,
382 return FilenameTypeAndDetails(kRegexFailure
, Nothing());
385 return FilenameTypeAndDetails(kSingleString
, Some(fileName
));
388 // Suspected userChromeJS script
389 rv
= RegexEval(kUCJSRegex
, fileName
, /* aOnlyMatch = */ true, regexMatch
);
391 return FilenameTypeAndDetails(kRegexFailure
, Nothing());
394 return FilenameTypeAndDetails(kSuspectedUserChromeJS
, Nothing());
398 // Something loaded via an about:// URI.
399 if (StringBeginsWith(fileName
, u
"about:"_ns
)) {
400 // Remove any querystrings and such
401 long int desired_length
= fileName
.Length();
402 long int possible_new_length
= 0;
404 possible_new_length
= fileName
.FindChar('?');
405 if (possible_new_length
!= -1 && possible_new_length
< desired_length
) {
406 desired_length
= possible_new_length
;
409 possible_new_length
= fileName
.FindChar('#');
410 if (possible_new_length
!= -1 && possible_new_length
< desired_length
) {
411 desired_length
= possible_new_length
;
414 auto subFileName
= Substring(fileName
, 0, desired_length
);
416 return FilenameTypeAndDetails(kAboutUri
, Some(subFileName
));
419 // Something loaded via a moz-extension:// URI.
420 if (StringBeginsWith(fileName
, u
"moz-extension://"_ns
)) {
421 if (!collectAdditionalExtensionData
) {
422 return FilenameTypeAndDetails(kExtensionURI
, Nothing());
425 nsAutoString sanitizedPathAndScheme
;
426 sanitizedPathAndScheme
.Append(u
"moz-extension://["_ns
);
428 nsCOMPtr
<nsIURI
> uri
;
429 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), fileName
);
431 // Return after adding ://[ so we know we failed here.
432 return FilenameTypeAndDetails(kExtensionURI
,
433 Some(sanitizedPathAndScheme
));
436 mozilla::extensions::URLInfo
url(uri
);
437 if (NS_IsMainThread()) {
438 // EPS is only usable on main thread
440 ExtensionPolicyService::GetSingleton().GetByHost(url
.Host());
443 policy
->GetId(addOnId
);
445 sanitizedPathAndScheme
.Append(addOnId
);
446 sanitizedPathAndScheme
.Append(u
": "_ns
);
447 sanitizedPathAndScheme
.Append(policy
->Name());
448 sanitizedPathAndScheme
.Append(u
"]"_ns
);
450 if (policy
->IsPrivileged()) {
451 sanitizedPathAndScheme
.Append(u
"P=1"_ns
);
453 sanitizedPathAndScheme
.Append(u
"P=0"_ns
);
456 sanitizedPathAndScheme
.Append(u
"failed finding addon by host]"_ns
);
459 sanitizedPathAndScheme
.Append(u
"can't get addon off main thread]"_ns
);
462 AppendUTF8toUTF16(url
.FilePath(), sanitizedPathAndScheme
);
463 return FilenameTypeAndDetails(kExtensionURI
, Some(sanitizedPathAndScheme
));
467 auto flags
= mozilla::widget::WinUtils::PathTransformFlags::Default
|
468 mozilla::widget::WinUtils::PathTransformFlags::RequireFilePath
;
469 nsAutoString
strSanitizedPath(fileName
);
470 if (widget::WinUtils::PreparePathForTelemetry(strSanitizedPath
, flags
)) {
471 DWORD cchDecodedUrl
= INTERNET_MAX_URL_LENGTH
;
472 WCHAR szOut
[INTERNET_MAX_URL_LENGTH
];
474 SAFECALL_URLMON_FUNC(CoInternetParseUrl
, fileName
.get(), PARSE_SCHEMA
, 0,
475 szOut
, INTERNET_MAX_URL_LENGTH
, &cchDecodedUrl
, 0);
476 if (hr
== S_OK
&& cchDecodedUrl
) {
477 nsAutoString sanitizedPathAndScheme
;
478 sanitizedPathAndScheme
.Append(szOut
);
479 if (sanitizedPathAndScheme
== u
"file"_ns
) {
480 sanitizedPathAndScheme
.Append(u
"://.../"_ns
);
481 sanitizedPathAndScheme
.Append(strSanitizedPath
);
483 return FilenameTypeAndDetails(kSanitizedWindowsURL
,
484 Some(sanitizedPathAndScheme
));
486 return FilenameTypeAndDetails(kSanitizedWindowsPath
,
487 Some(strSanitizedPath
));
492 if (!NS_IsMainThread()) {
493 return FilenameTypeAndDetails(kOtherWorker
, Nothing());
495 return FilenameTypeAndDetails(kOther
, Nothing());
499 // Crash String must be safe from a telemetry point of view.
500 // This will be ensured when this function is used.
501 void PossiblyCrash(const char* aPrefSuffix
, const char* aUnsafeCrashString
,
502 const nsCString
& aSafeCrashString
) {
503 if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
504 // We only crash in the parent (unfortunately) because it's
505 // the only place we can be sure that our only-crash-once
506 // pref-writing works.
509 if (!NS_IsMainThread()) {
510 // Setting a pref off the main thread causes ContentParent to observe the
511 // pref set, resulting in a Release Assertion when it tries to update the
512 // child off main thread. So don't do any of this off main thread. (Which
513 // is a bit of a blind spot for this purpose...)
517 nsCString
previous_crashes("security.crash_tracking.");
518 previous_crashes
.Append(aPrefSuffix
);
519 previous_crashes
.Append(".prevCrashes");
521 nsCString
max_crashes("security.crash_tracking.");
522 max_crashes
.Append(aPrefSuffix
);
523 max_crashes
.Append(".maxCrashes");
525 int32_t numberOfPreviousCrashes
= 0;
526 numberOfPreviousCrashes
= Preferences::GetInt(previous_crashes
.get(), 0);
528 int32_t maxAllowableCrashes
= 0;
529 maxAllowableCrashes
= Preferences::GetInt(max_crashes
.get(), 0);
531 if (numberOfPreviousCrashes
>= maxAllowableCrashes
) {
536 Preferences::SetInt(previous_crashes
.get(), ++numberOfPreviousCrashes
);
541 nsCOMPtr
<nsIPrefService
> prefsCom
= Preferences::GetService();
542 Preferences
* prefs
= static_cast<Preferences
*>(prefsCom
.get());
544 if (!prefs
->AllowOffMainThreadSave()) {
545 // Do not crash if we can't save prefs off the main thread
549 rv
= prefs
->SavePrefFileBlocking();
550 if (!NS_FAILED(rv
)) {
551 // We can only use this in local builds where we don't send stuff up to the
552 // crash reporter because it has user private data.
553 // MOZ_CRASH_UNSAFE_PRINTF("%s",
554 // nsContentSecurityUtils::SmartFormatCrashString(aUnsafeCrashString));
555 MOZ_CRASH_UNSAFE_PRINTF(
557 nsContentSecurityUtils::SmartFormatCrashString(aSafeCrashString
.get()));
562 class EvalUsageNotificationRunnable final
: public Runnable
{
564 EvalUsageNotificationRunnable(bool aIsSystemPrincipal
,
565 NS_ConvertUTF8toUTF16
& aFileNameA
,
566 uint64_t aWindowID
, uint32_t aLineNumber
,
567 uint32_t aColumnNumber
)
568 : mozilla::Runnable("EvalUsageNotificationRunnable"),
569 mIsSystemPrincipal(aIsSystemPrincipal
),
570 mFileNameA(aFileNameA
),
571 mWindowID(aWindowID
),
572 mLineNumber(aLineNumber
),
573 mColumnNumber(aColumnNumber
) {}
575 NS_IMETHOD
Run() override
{
576 nsContentSecurityUtils::NotifyEvalUsage(
577 mIsSystemPrincipal
, mFileNameA
, mWindowID
, mLineNumber
, mColumnNumber
);
584 bool mIsSystemPrincipal
;
585 NS_ConvertUTF8toUTF16 mFileNameA
;
587 uint32_t mLineNumber
;
588 uint32_t mColumnNumber
;
592 bool nsContentSecurityUtils::IsEvalAllowed(JSContext
* cx
,
593 bool aIsSystemPrincipal
,
594 const nsAString
& aScript
) {
595 // This allowlist contains files that are permanently allowed to use
596 // eval()-like functions. It will ideally be restricted to files that are
597 // exclusively used in testing contexts.
598 static nsLiteralCString evalAllowlist
[] = {
599 // Test-only third-party library
600 "resource://testing-common/sinon-7.2.7.js"_ns
,
602 "resource://testing-common/content-task.js"_ns
,
604 // Tracked by Bug 1584605
605 "resource://gre/modules/translation/cld-worker.js"_ns
,
607 // require.js implements a script loader for workers. It uses eval
608 // to load the script; but injection is only possible in situations
609 // that you could otherwise control script that gets executed, so
610 // it is okay to allow eval() as it adds no additional attack surface.
611 // Bug 1584564 tracks requiring safe usage of require.js
612 "resource://gre/modules/workers/require.js"_ns
,
614 // The profiler's symbolication code uses a wasm module to extract symbols
615 // from the binary files result of local builds.
617 "resource://devtools/client/performance-new/symbolication.jsm.js"_ns
,
619 // The Browser Toolbox/Console
623 // We also permit two specific idioms in eval()-like contexts. We'd like to
624 // elminate these too; but there are in-the-wild Mozilla privileged extensions
626 static constexpr auto sAllowedEval1
= u
"this"_ns
;
627 static constexpr auto sAllowedEval2
=
628 u
"function anonymous(\n) {\nreturn this\n}"_ns
;
630 if (MOZ_LIKELY(!aIsSystemPrincipal
&& !XRE_IsE10sParentProcess())) {
631 // We restrict eval in the system principal and parent process.
632 // Other uses (like web content and null principal) are allowed.
636 if (JS::ContextOptionsRef(cx
).disableEvalSecurityChecks()) {
637 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
638 ("Allowing eval() because this JSContext was set to allow it"));
642 if (aIsSystemPrincipal
&&
643 StaticPrefs::security_allow_eval_with_system_principal()) {
644 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
645 ("Allowing eval() with System Principal because allowing pref is "
650 if (XRE_IsE10sParentProcess() &&
651 StaticPrefs::security_allow_eval_in_parent_process()) {
652 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
653 ("Allowing eval() in parent process because allowing pref is "
659 if (MOZ_UNLIKELY(sJSHacksPresent
)) {
661 sCSMLog
, LogLevel::Debug
,
662 ("Allowing eval() %s because some "
663 "JS hacks may be present.",
664 (aIsSystemPrincipal
? "with System Principal" : "in parent process")));
668 if (XRE_IsE10sParentProcess() &&
669 !StaticPrefs::extensions_webextensions_remote()) {
670 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
671 ("Allowing eval() in parent process because the web extension "
672 "process is disabled"));
676 // We permit these two common idioms to get access to the global JS object
677 if (!aScript
.IsEmpty() &&
678 (aScript
== sAllowedEval1
|| aScript
== sAllowedEval2
)) {
680 sCSMLog
, LogLevel::Debug
,
681 ("Allowing eval() %s because a key string is "
683 (aIsSystemPrincipal
? "with System Principal" : "in parent process")));
687 // Check the allowlist for the provided filename. getFilename is a helper
689 nsAutoCString fileName
;
690 uint32_t lineNumber
= 0, columnNumber
= 0;
691 nsJSUtils::GetCallingLocation(cx
, fileName
, &lineNumber
, &columnNumber
);
692 if (fileName
.IsEmpty()) {
693 fileName
= "unknown-file"_ns
;
696 NS_ConvertUTF8toUTF16
fileNameA(fileName
);
697 for (const nsLiteralCString
& allowlistEntry
: evalAllowlist
) {
698 // checking if current filename begins with entry, because JS Engine
699 // gives us additional stuff for code inside eval or Function ctor
700 // e.g., "require.js > Function"
701 if (StringBeginsWith(fileName
, allowlistEntry
)) {
702 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
703 ("Allowing eval() %s because the containing "
704 "file is in the allowlist",
705 (aIsSystemPrincipal
? "with System Principal"
706 : "in parent process")));
711 // Send Telemetry and Log to the Console
712 uint64_t windowID
= nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx
);
713 if (NS_IsMainThread()) {
714 nsContentSecurityUtils::NotifyEvalUsage(aIsSystemPrincipal
, fileNameA
,
715 windowID
, lineNumber
, columnNumber
);
717 auto runnable
= new EvalUsageNotificationRunnable(
718 aIsSystemPrincipal
, fileNameA
, windowID
, lineNumber
, columnNumber
);
719 NS_DispatchToMainThread(runnable
);
723 MOZ_LOG(sCSMLog
, LogLevel::Error
,
724 ("Blocking eval() %s from file %s and script "
726 (aIsSystemPrincipal
? "with System Principal" : "in parent process"),
727 fileName
.get(), NS_ConvertUTF16toUTF8(aScript
).get()));
730 #if defined(DEBUG) || defined(FUZZING)
731 auto crashString
= nsContentSecurityUtils::SmartFormatCrashString(
732 NS_ConvertUTF16toUTF8(aScript
).get(), fileName
.get(),
734 ? "Blocking eval() with System Principal with script %s from file %s"
735 : "Blocking eval() in parent process with script %s from file %s"));
736 MOZ_CRASH_UNSAFE_PRINTF("%s", crashString
.get());
743 void nsContentSecurityUtils::NotifyEvalUsage(bool aIsSystemPrincipal
,
744 NS_ConvertUTF8toUTF16
& aFileNameA
,
746 uint32_t aLineNumber
,
747 uint32_t aColumnNumber
) {
749 Telemetry::EventID eventType
=
750 aIsSystemPrincipal
? Telemetry::EventID::Security_Evalusage_Systemcontext
751 : Telemetry::EventID::Security_Evalusage_Parentprocess
;
753 FilenameTypeAndDetails fileNameTypeAndDetails
=
754 FilenameToFilenameType(aFileNameA
, false);
755 mozilla::Maybe
<nsTArray
<EventExtraEntry
>> extra
;
756 if (fileNameTypeAndDetails
.second
.isSome()) {
757 extra
= Some
<nsTArray
<EventExtraEntry
>>({EventExtraEntry
{
759 NS_ConvertUTF16toUTF8(fileNameTypeAndDetails
.second
.value())}});
763 if (!sTelemetryEventEnabled
.exchange(true)) {
764 sTelemetryEventEnabled
= true;
765 Telemetry::SetEventRecordingEnabled("security"_ns
, true);
767 Telemetry::RecordEvent(eventType
, mozilla::Some(fileNameTypeAndDetails
.first
),
770 // Report an error to console
771 nsCOMPtr
<nsIConsoleService
> console(
772 do_GetService(NS_CONSOLESERVICE_CONTRACTID
));
776 nsCOMPtr
<nsIScriptError
> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
));
780 nsCOMPtr
<nsIStringBundle
> bundle
;
781 nsCOMPtr
<nsIStringBundleService
> stringService
=
782 mozilla::components::StringBundle::Service();
783 if (!stringService
) {
786 stringService
->CreateBundle(
787 "chrome://global/locale/security/security.properties",
788 getter_AddRefs(bundle
));
792 nsAutoString message
;
793 AutoTArray
<nsString
, 1> formatStrings
= {aFileNameA
};
794 nsresult rv
= bundle
->FormatStringFromName("RestrictBrowserEvalUsage",
795 formatStrings
, message
);
800 rv
= error
->InitWithWindowID(message
, aFileNameA
, u
""_ns
, aLineNumber
,
801 aColumnNumber
, nsIScriptError::errorFlag
,
802 "BrowserEvalUsage", aWindowID
,
803 true /* From chrome context */);
807 console
->LogMessage(error
);
811 void nsContentSecurityUtils::DetectJsHacks() {
812 // We can only perform the check of this preference on the Main Thread
813 // (because a String-based preference check is only safe on Main Thread.)
814 // In theory, it would be possible that a separate thread could get here
815 // before the main thread, resulting in the other thread not being able to
816 // perform this check, but the odds of that are small (and probably zero.)
817 if (!NS_IsMainThread()) {
821 // If the pref service isn't available, do nothing and re-do this later.
822 if (!Preferences::IsServiceAvailable()) {
826 // No need to check again.
827 if (MOZ_LIKELY(sJSHacksChecked
|| sJSHacksPresent
)) {
831 sJSHacksChecked
= true;
833 // This preference is required by bootstrapLoader.xpi, which is an
834 // alternate way to load legacy-style extensions. It only works on
835 // DevEdition/Nightly.
836 bool xpinstallSignatures
;
837 rv
= Preferences::GetBool("xpinstall.signatures.required",
838 &xpinstallSignatures
, PrefValueKind::Default
);
839 if (!NS_FAILED(rv
) && !xpinstallSignatures
) {
840 sJSHacksPresent
= true;
843 rv
= Preferences::GetBool("xpinstall.signatures.required",
844 &xpinstallSignatures
, PrefValueKind::User
);
845 if (!NS_FAILED(rv
) && !xpinstallSignatures
) {
846 sJSHacksPresent
= true;
850 // This preference is a file used for autoconfiguration of Firefox
851 // by administrators. It has also been (ab)used by the userChromeJS
852 // project to run legacy-style 'extensions', some of which use eval,
853 // all of which run in the System Principal context.
854 nsAutoString jsConfigPref
;
855 rv
= Preferences::GetString("general.config.filename", jsConfigPref
,
856 PrefValueKind::Default
);
857 if (!NS_FAILED(rv
) && !jsConfigPref
.IsEmpty()) {
858 sJSHacksPresent
= true;
861 rv
= Preferences::GetString("general.config.filename", jsConfigPref
,
862 PrefValueKind::User
);
863 if (!NS_FAILED(rv
) && !jsConfigPref
.IsEmpty()) {
864 sJSHacksPresent
= true;
868 // These preferences are for autoconfiguration of Firefox by admins.
869 // The first will load a file over the network; the second will
870 // fall back to a local file if the network is unavailable
871 nsAutoString configUrlPref
;
872 rv
= Preferences::GetString("autoadmin.global_config_url", configUrlPref
,
873 PrefValueKind::Default
);
874 if (!NS_FAILED(rv
) && !configUrlPref
.IsEmpty()) {
875 sJSHacksPresent
= true;
878 rv
= Preferences::GetString("autoadmin.global_config_url", configUrlPref
,
879 PrefValueKind::User
);
880 if (!NS_FAILED(rv
) && !configUrlPref
.IsEmpty()) {
881 sJSHacksPresent
= true;
885 bool failOverToCache
;
886 rv
= Preferences::GetBool("autoadmin.failover_to_cached", &failOverToCache
,
887 PrefValueKind::Default
);
888 if (!NS_FAILED(rv
) && failOverToCache
) {
889 sJSHacksPresent
= true;
892 rv
= Preferences::GetBool("autoadmin.failover_to_cached", &failOverToCache
,
893 PrefValueKind::User
);
894 if (!NS_FAILED(rv
) && failOverToCache
) {
895 sJSHacksPresent
= true;
900 void nsContentSecurityUtils::DetectCssHacks() {
901 // We can only perform the check of this preference on the Main Thread
902 // It's possible that this function may therefore race and we expect the
903 // caller to ensure that the checks have actually happened.
904 if (!NS_IsMainThread()) {
908 // If the pref service isn't available, do nothing and re-do this later.
909 if (!Preferences::IsServiceAvailable()) {
913 // No need to check again.
914 if (MOZ_LIKELY(sCSSHacksChecked
|| sCSSHacksPresent
)) {
918 // This preference is a bool to see if userChrome css is loaded
919 bool customStylesPresent
= Preferences::GetBool(
920 "toolkit.legacyUserProfileCustomizations.stylesheets", false);
921 if (customStylesPresent
) {
922 sCSSHacksPresent
= true;
925 sCSSHacksChecked
= true;
929 nsresult
nsContentSecurityUtils::GetHttpChannelFromPotentialMultiPart(
930 nsIChannel
* aChannel
, nsIHttpChannel
** aHttpChannel
) {
931 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
933 httpChannel
.forget(aHttpChannel
);
937 nsCOMPtr
<nsIMultiPartChannel
> multipart
= do_QueryInterface(aChannel
);
939 *aHttpChannel
= nullptr;
943 nsCOMPtr
<nsIChannel
> baseChannel
;
944 nsresult rv
= multipart
->GetBaseChannel(getter_AddRefs(baseChannel
));
945 if (NS_WARN_IF(NS_FAILED(rv
))) {
949 httpChannel
= do_QueryInterface(baseChannel
);
950 httpChannel
.forget(aHttpChannel
);
955 nsresult
CheckCSPFrameAncestorPolicy(nsIChannel
* aChannel
,
956 nsIContentSecurityPolicy
** aOutCSP
) {
957 MOZ_ASSERT(aChannel
);
959 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
960 ExtContentPolicyType contentType
= loadInfo
->GetExternalContentPolicyType();
961 // frame-ancestor check only makes sense for subdocument and object loads,
962 // if this is not a load of such type, there is nothing to do here.
963 if (contentType
!= ExtContentPolicy::TYPE_SUBDOCUMENT
&&
964 contentType
!= ExtContentPolicy::TYPE_OBJECT
) {
968 // CSP can only hang off an http channel, if this channel is not
969 // an http channel then there is nothing to do here,
970 // except with add-ons, where the CSP is stored in a WebExtensionPolicy.
971 nsCOMPtr
<nsIHttpChannel
> httpChannel
;
972 nsresult rv
= nsContentSecurityUtils::GetHttpChannelFromPotentialMultiPart(
973 aChannel
, getter_AddRefs(httpChannel
));
974 if (NS_WARN_IF(NS_FAILED(rv
))) {
978 nsAutoCString tCspHeaderValue
, tCspROHeaderValue
;
980 Unused
<< httpChannel
->GetResponseHeader("content-security-policy"_ns
,
983 Unused
<< httpChannel
->GetResponseHeader(
984 "content-security-policy-report-only"_ns
, tCspROHeaderValue
);
986 // if there are no CSP values, then there is nothing to do here.
987 if (tCspHeaderValue
.IsEmpty() && tCspROHeaderValue
.IsEmpty()) {
992 nsCOMPtr
<nsIPrincipal
> resultPrincipal
;
993 rv
= nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
994 aChannel
, getter_AddRefs(resultPrincipal
));
995 NS_ENSURE_SUCCESS(rv
, rv
);
997 RefPtr
<extensions::WebExtensionPolicy
> addonPolicy
;
999 addonPolicy
= BasePrincipal::Cast(resultPrincipal
)->AddonPolicy();
1001 // Neither a HTTP channel, nor a moz-extension:-resource.
1002 // CSP is not supported.
1007 RefPtr
<nsCSPContext
> csp
= new nsCSPContext();
1008 nsCOMPtr
<nsIURI
> selfURI
;
1009 nsAutoString referrerSpec
;
1011 aChannel
->GetURI(getter_AddRefs(selfURI
));
1012 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= httpChannel
->GetReferrerInfo();
1014 referrerInfo
->GetComputedReferrerSpec(referrerSpec
);
1017 // aChannel::GetURI would return the jar: or file:-URI for extensions.
1018 // Use the "final" URI to get the actual moz-extension:-URL.
1019 NS_GetFinalChannelURI(aChannel
, getter_AddRefs(selfURI
));
1022 uint64_t innerWindowID
= loadInfo
->GetInnerWindowID();
1024 rv
= csp
->SetRequestContextWithPrincipal(resultPrincipal
, selfURI
,
1025 referrerSpec
, innerWindowID
);
1026 if (NS_WARN_IF(NS_FAILED(rv
))) {
1031 csp
->AppendPolicy(addonPolicy
->BaseCSP(), false, false);
1032 csp
->AppendPolicy(addonPolicy
->ExtensionPageCSP(), false, false);
1034 NS_ConvertASCIItoUTF16
cspHeaderValue(tCspHeaderValue
);
1035 NS_ConvertASCIItoUTF16
cspROHeaderValue(tCspROHeaderValue
);
1037 // ----- if there's a full-strength CSP header, apply it.
1038 if (!cspHeaderValue
.IsEmpty()) {
1039 rv
= CSP_AppendCSPFromHeader(csp
, cspHeaderValue
, false);
1040 NS_ENSURE_SUCCESS(rv
, rv
);
1043 // ----- if there's a report-only CSP header, apply it.
1044 if (!cspROHeaderValue
.IsEmpty()) {
1045 rv
= CSP_AppendCSPFromHeader(csp
, cspROHeaderValue
, true);
1046 NS_ENSURE_SUCCESS(rv
, rv
);
1050 // ----- Enforce frame-ancestor policy on any applied policies
1051 bool safeAncestry
= false;
1052 // PermitsAncestry sends violation reports when necessary
1053 rv
= csp
->PermitsAncestry(loadInfo
, &safeAncestry
);
1055 if (NS_FAILED(rv
) || !safeAncestry
) {
1056 // stop! ERROR page!
1057 return NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION
;
1060 // return the CSP for x-frame-options check
1061 csp
.forget(aOutCSP
);
1066 void EnforceCSPFrameAncestorPolicy(nsIChannel
* aChannel
,
1067 const nsresult
& aError
) {
1068 if (aError
== NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION
) {
1069 aChannel
->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION
);
1073 void EnforceXFrameOptionsCheck(nsIChannel
* aChannel
,
1074 nsIContentSecurityPolicy
* aCsp
) {
1075 MOZ_ASSERT(aChannel
);
1076 bool isFrameOptionsIgnored
= false;
1077 // check for XFO options
1078 // XFO checks can be skipped if there are frame ancestors
1079 if (!FramingChecker::CheckFrameOptions(aChannel
, aCsp
,
1080 isFrameOptionsIgnored
)) {
1081 // stop! ERROR page!
1082 aChannel
->Cancel(NS_ERROR_XFO_VIOLATION
);
1085 if (isFrameOptionsIgnored
) {
1086 // log warning to console that xfo is ignored because of CSP
1087 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1088 uint64_t innerWindowID
= loadInfo
->GetInnerWindowID();
1089 bool privateWindow
= !!loadInfo
->GetOriginAttributes().mPrivateBrowsingId
;
1090 AutoTArray
<nsString
, 2> params
= {u
"x-frame-options"_ns
,
1091 u
"frame-ancestors"_ns
};
1092 CSP_LogLocalizedStr("IgnoringSrcBecauseOfDirective", params
,
1093 u
""_ns
, // no sourcefile
1094 u
""_ns
, // no scriptsample
1096 0, // no columnnumber
1097 nsIScriptError::warningFlag
,
1098 "IgnoringSrcBecauseOfDirective"_ns
, innerWindowID
,
1104 void nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(
1105 nsIChannel
* aChannel
) {
1106 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
1107 nsresult rv
= CheckCSPFrameAncestorPolicy(aChannel
, getter_AddRefs(csp
));
1109 if (NS_FAILED(rv
)) {
1110 EnforceCSPFrameAncestorPolicy(aChannel
, rv
);
1114 // X-Frame-Options needs to be enforced after CSP frame-ancestors
1115 // checks because if frame-ancestors is present, then x-frame-options
1116 // will be discarded
1117 EnforceXFrameOptionsCheck(aChannel
, csp
);
1120 bool nsContentSecurityUtils::CheckCSPFrameAncestorAndXFO(nsIChannel
* aChannel
) {
1121 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
1122 nsresult rv
= CheckCSPFrameAncestorPolicy(aChannel
, getter_AddRefs(csp
));
1124 if (NS_FAILED(rv
)) {
1125 EnforceCSPFrameAncestorPolicy(aChannel
, rv
);
1129 bool isFrameOptionsIgnored
= false;
1131 return FramingChecker::CheckFrameOptions(aChannel
, csp
,
1132 isFrameOptionsIgnored
);
1137 void nsContentSecurityUtils::AssertAboutPageHasCSP(Document
* aDocument
) {
1138 // We want to get to a point where all about: pages ship with a CSP. This
1139 // assertion ensures that we can not deploy new about: pages without a CSP.
1140 // Please note that any about: page should not use inline JS or inline CSS,
1141 // and instead should load JS and CSS from an external file (*.js, *.css)
1142 // which allows us to apply a strong CSP omitting 'unsafe-inline'. Ideally,
1143 // the CSP allows precisely the resources that need to be loaded; but it
1144 // should at least be as strong as:
1145 // <meta http-equiv="Content-Security-Policy" content="default-src chrome:;
1146 // object-src 'none'"/>
1148 // Check if we should skip the assertion
1149 if (StaticPrefs::dom_security_skip_about_page_has_csp_assert()) {
1153 // Check if we are loading an about: URI at all
1154 nsCOMPtr
<nsIURI
> documentURI
= aDocument
->GetDocumentURI();
1155 if (!documentURI
->SchemeIs("about")) {
1159 nsCOMPtr
<nsIContentSecurityPolicy
> csp
= aDocument
->GetCsp();
1160 bool foundDefaultSrc
= false;
1161 bool foundObjectSrc
= false;
1162 bool foundUnsafeEval
= false;
1163 bool foundUnsafeInline
= false;
1164 bool foundScriptSrc
= false;
1165 bool foundWorkerSrc
= false;
1166 bool foundWebScheme
= false;
1168 uint32_t policyCount
= 0;
1169 csp
->GetPolicyCount(&policyCount
);
1170 nsAutoString parsedPolicyStr
;
1171 for (uint32_t i
= 0; i
< policyCount
; ++i
) {
1172 csp
->GetPolicyString(i
, parsedPolicyStr
);
1173 if (parsedPolicyStr
.Find(u
"default-src") >= 0) {
1174 foundDefaultSrc
= true;
1176 if (parsedPolicyStr
.Find(u
"object-src 'none'") >= 0) {
1177 foundObjectSrc
= true;
1179 if (parsedPolicyStr
.Find(u
"'unsafe-eval'") >= 0) {
1180 foundUnsafeEval
= true;
1182 if (parsedPolicyStr
.Find(u
"'unsafe-inline'") >= 0) {
1183 foundUnsafeInline
= true;
1185 if (parsedPolicyStr
.Find(u
"script-src") >= 0) {
1186 foundScriptSrc
= true;
1188 if (parsedPolicyStr
.Find(u
"worker-src") >= 0) {
1189 foundWorkerSrc
= true;
1191 if (parsedPolicyStr
.Find(u
"http:") >= 0 ||
1192 parsedPolicyStr
.Find(u
"https:") >= 0) {
1193 foundWebScheme
= true;
1198 // Check if we should skip the allowlist and assert right away. Please note
1199 // that this pref can and should only be set for automated testing.
1200 if (StaticPrefs::dom_security_skip_about_page_csp_allowlist_and_assert()) {
1201 NS_ASSERTION(foundDefaultSrc
, "about: page must have a CSP");
1205 nsAutoCString aboutSpec
;
1206 documentURI
->GetSpec(aboutSpec
);
1207 ToLowerCase(aboutSpec
);
1209 // This allowlist contains about: pages that are permanently allowed to
1210 // render without a CSP applied.
1211 static nsLiteralCString sAllowedAboutPagesWithNoCSP
[] = {
1212 // about:blank is a special about page -> no CSP
1214 // about:srcdoc is a special about page -> no CSP
1216 // about:sync-log displays plain text only -> no CSP
1217 "about:sync-log"_ns
,
1218 // about:logo just displays the firefox logo -> no CSP
1220 // about:sync is a special mozilla-signed developer addon with low usage ->
1223 # if defined(ANDROID)
1228 for (const nsLiteralCString
& allowlistEntry
: sAllowedAboutPagesWithNoCSP
) {
1229 // please note that we perform a substring match here on purpose,
1230 // so we don't have to deal and parse out all the query arguments
1231 // the various about pages rely on.
1232 if (StringBeginsWith(aboutSpec
, allowlistEntry
)) {
1237 MOZ_ASSERT(foundDefaultSrc
,
1238 "about: page must contain a CSP including default-src");
1239 MOZ_ASSERT(foundObjectSrc
,
1240 "about: page must contain a CSP denying object-src");
1242 // preferences and downloads allow legacy inline scripts through hash src.
1243 MOZ_ASSERT(!foundScriptSrc
||
1244 StringBeginsWith(aboutSpec
, "about:preferences"_ns
) ||
1245 StringBeginsWith(aboutSpec
, "about:downloads"_ns
) ||
1246 StringBeginsWith(aboutSpec
, "about:newtab"_ns
) ||
1247 StringBeginsWith(aboutSpec
, "about:logins"_ns
) ||
1248 StringBeginsWith(aboutSpec
, "about:compat"_ns
) ||
1249 StringBeginsWith(aboutSpec
, "about:welcome"_ns
) ||
1250 StringBeginsWith(aboutSpec
, "about:profiling"_ns
) ||
1251 StringBeginsWith(aboutSpec
, "about:studies"_ns
) ||
1252 StringBeginsWith(aboutSpec
, "about:home"_ns
),
1253 "about: page must not contain a CSP including script-src");
1255 MOZ_ASSERT(!foundWorkerSrc
,
1256 "about: page must not contain a CSP including worker-src");
1258 // addons, preferences, debugging, ion, devtools all have to allow some
1259 // remote web resources
1260 MOZ_ASSERT(!foundWebScheme
||
1261 StringBeginsWith(aboutSpec
, "about:preferences"_ns
) ||
1262 StringBeginsWith(aboutSpec
, "about:addons"_ns
) ||
1263 StringBeginsWith(aboutSpec
, "about:newtab"_ns
) ||
1264 StringBeginsWith(aboutSpec
, "about:debugging"_ns
) ||
1265 StringBeginsWith(aboutSpec
, "about:ion"_ns
) ||
1266 StringBeginsWith(aboutSpec
, "about:compat"_ns
) ||
1267 StringBeginsWith(aboutSpec
, "about:logins"_ns
) ||
1268 StringBeginsWith(aboutSpec
, "about:home"_ns
) ||
1269 StringBeginsWith(aboutSpec
, "about:welcome"_ns
) ||
1270 StringBeginsWith(aboutSpec
, "about:devtools"_ns
) ||
1271 StringBeginsWith(aboutSpec
, "about:pocket-saved"_ns
) ||
1272 StringBeginsWith(aboutSpec
, "about:pocket-home"_ns
),
1273 "about: page must not contain a CSP including a web scheme");
1275 if (aDocument
->IsExtensionPage()) {
1276 // Extensions have two CSP policies applied where the baseline CSP
1277 // includes 'unsafe-eval' and 'unsafe-inline', hence we have to skip
1278 // the 'unsafe-eval' and 'unsafe-inline' assertions for extension
1283 MOZ_ASSERT(!foundUnsafeEval
,
1284 "about: page must not contain a CSP including 'unsafe-eval'");
1286 static nsLiteralCString sLegacyUnsafeInlineAllowList
[] = {
1287 // Bug 1579160: Remove 'unsafe-inline' from style-src within
1288 // about:preferences
1289 "about:preferences"_ns
,
1290 // Bug 1571346: Remove 'unsafe-inline' from style-src within about:addons
1292 // Bug 1584485: Remove 'unsafe-inline' from style-src within:
1301 for (const nsLiteralCString
& aUnsafeInlineEntry
:
1302 sLegacyUnsafeInlineAllowList
) {
1303 // please note that we perform a substring match here on purpose,
1304 // so we don't have to deal and parse out all the query arguments
1305 // the various about pages rely on.
1306 if (StringBeginsWith(aboutSpec
, aUnsafeInlineEntry
)) {
1311 MOZ_ASSERT(!foundUnsafeInline
,
1312 "about: page must not contain a CSP including 'unsafe-inline'");
1317 bool nsContentSecurityUtils::ValidateScriptFilename(JSContext
* cx
,
1318 const char* aFilename
) {
1319 // If the pref is permissive, allow everything
1320 if (StaticPrefs::security_allow_parent_unrestricted_js_loads()) {
1324 // If we're not in the parent process allow everything (presently)
1325 if (!XRE_IsE10sParentProcess()) {
1329 // If we have allowed eval (because of a user configuration or more
1330 // likely a test has requested it), and the script is an eval, allow it.
1331 NS_ConvertUTF8toUTF16
filenameU(aFilename
);
1332 if (StaticPrefs::security_allow_eval_with_system_principal() ||
1333 StaticPrefs::security_allow_eval_in_parent_process()) {
1334 if (StringEndsWith(filenameU
, u
"> eval"_ns
)) {
1341 if (MOZ_UNLIKELY(!sJSHacksChecked
)) {
1343 sCSMLog
, LogLevel::Debug
,
1344 ("Allowing a javascript load of %s because "
1345 "we have not yet been able to determine if JS hacks may be present",
1350 if (MOZ_UNLIKELY(sJSHacksPresent
)) {
1351 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1352 ("Allowing a javascript load of %s because "
1353 "some JS hacks may be present",
1358 if (XRE_IsE10sParentProcess() &&
1359 !StaticPrefs::extensions_webextensions_remote()) {
1360 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1361 ("Allowing a javascript load of %s because the web extension "
1362 "process is disabled.",
1367 if (StringBeginsWith(filenameU
, u
"chrome://"_ns
)) {
1368 // If it's a chrome:// url, allow it
1371 if (StringBeginsWith(filenameU
, u
"resource://"_ns
)) {
1372 // If it's a resource:// url, allow it
1375 if (StringBeginsWith(filenameU
, u
"file://"_ns
)) {
1376 // We will temporarily allow all file:// URIs through for now
1379 if (StringBeginsWith(filenameU
, u
"jar:file://"_ns
)) {
1380 // We will temporarily allow all jar URIs through for now
1383 if (filenameU
.Equals(u
"about:sync-log"_ns
)) {
1384 // about:sync-log runs in the parent process and displays a directory
1385 // listing. The listing has inline javascript that executes on load.
1389 if (StringBeginsWith(filenameU
, u
"moz-extension://"_ns
)) {
1390 nsCOMPtr
<nsIURI
> uri
;
1391 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aFilename
);
1392 if (!NS_FAILED(rv
) && NS_IsMainThread()) {
1393 mozilla::extensions::URLInfo
url(uri
);
1395 ExtensionPolicyService::GetSingleton().GetByHost(url
.Host());
1397 if (policy
&& policy
->IsPrivileged()) {
1398 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1399 ("Allowing a javascript load of %s because the web extension "
1400 "it is associated with is privileged.",
1405 } else if (!NS_IsMainThread()) {
1406 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
1407 if (workerPrivate
&& workerPrivate
->IsPrivilegedAddonGlobal()) {
1408 MOZ_LOG(sCSMLog
, LogLevel::Debug
,
1409 ("Allowing a javascript load of %s because the web extension "
1410 "it is associated with is privileged.",
1416 auto kAllowedFilenamesExact
= {
1417 // Allow through the injection provided by about:sync addon
1418 u
"data:,new function() {\n const { AboutSyncRedirector } = ChromeUtils.import(\"chrome://aboutsync/content/AboutSyncRedirector.js\");\n AboutSyncRedirector.register();\n}"_ns
,
1421 for (auto allowedFilename
: kAllowedFilenamesExact
) {
1422 if (filenameU
== allowedFilename
) {
1427 auto kAllowedFilenamesPrefix
= {
1428 // Until 371900 is fixed, we need to do something about about:downloads
1429 // and this is the most reasonable. See 1727770
1430 u
"about:downloads"_ns
,
1431 // We think this is the same problem as about:downloads
1432 u
"about:preferences"_ns
,
1433 // Browser console will give a filename of 'debugger' See 1763943
1434 // Sometimes it's 'debugger eager eval code', other times just 'debugger
1438 for (auto allowedFilenamePrefix
: kAllowedFilenamesPrefix
) {
1439 if (StringBeginsWith(filenameU
, allowedFilenamePrefix
)) {
1445 MOZ_LOG(sCSMLog
, LogLevel::Error
,
1446 ("ValidateScriptFilename Failed: %s\n", aFilename
));
1449 FilenameTypeAndDetails fileNameTypeAndDetails
=
1450 FilenameToFilenameType(filenameU
, true);
1452 Telemetry::EventID eventType
=
1453 Telemetry::EventID::Security_Javascriptload_Parentprocess
;
1455 mozilla::Maybe
<nsTArray
<EventExtraEntry
>> extra
;
1456 if (fileNameTypeAndDetails
.second
.isSome()) {
1457 extra
= Some
<nsTArray
<EventExtraEntry
>>({EventExtraEntry
{
1459 NS_ConvertUTF16toUTF8(fileNameTypeAndDetails
.second
.value())}});
1464 if (!sTelemetryEventEnabled
.exchange(true)) {
1465 sTelemetryEventEnabled
= true;
1466 Telemetry::SetEventRecordingEnabled("security"_ns
, true);
1468 Telemetry::RecordEvent(eventType
, mozilla::Some(fileNameTypeAndDetails
.first
),
1471 #if defined(DEBUG) || defined(FUZZING)
1472 auto crashString
= nsContentSecurityUtils::SmartFormatCrashString(
1474 fileNameTypeAndDetails
.second
.isSome()
1475 ? NS_ConvertUTF16toUTF8(fileNameTypeAndDetails
.second
.value()).get()
1477 "Blocking a script load %s from file %s");
1478 MOZ_CRASH_UNSAFE_PRINTF("%s", crashString
.get());
1479 #elif defined(NIGHTLY_BUILD)
1480 // Cause a crash (if we've never crashed before and we can ensure we won't do
1482 // The details in the second arg, passed to UNSAFE_PRINTF, are also included
1483 // in Event Telemetry and have received data review.
1484 if (fileNameTypeAndDetails
.second
.isSome()) {
1485 PossiblyCrash("js_load_1", aFilename
,
1486 NS_ConvertUTF16toUTF8(fileNameTypeAndDetails
.second
.value()));
1488 PossiblyCrash("js_load_1", aFilename
, "(None)"_ns
);
1492 // Presently we are only enforcing restrictions for the script filename
1493 // on Nightly. On all channels we are reporting Telemetry. In the future we
1494 // will assert in debug builds and return false to prevent execution in
1495 // non-debug builds.
1496 #ifdef NIGHTLY_BUILD
1504 void nsContentSecurityUtils::LogMessageToConsole(nsIHttpChannel
* aChannel
,
1506 nsCOMPtr
<nsIURI
> uri
;
1507 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
1508 if (NS_FAILED(rv
)) {
1512 uint64_t windowID
= 0;
1513 rv
= aChannel
->GetTopLevelContentWindowId(&windowID
);
1514 if (NS_WARN_IF(NS_FAILED(rv
))) {
1518 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1519 loadInfo
->GetInnerWindowID(&windowID
);
1522 nsAutoString localizedMsg
;
1525 AutoTArray
<nsString
, 1> params
= {NS_ConvertUTF8toUTF16(spec
)};
1526 rv
= nsContentUtils::FormatLocalizedString(
1527 nsContentUtils::eSECURITY_PROPERTIES
, aMsg
, params
, localizedMsg
);
1528 if (NS_WARN_IF(NS_FAILED(rv
))) {
1532 nsContentUtils::ReportToConsoleByWindowID(
1533 localizedMsg
, nsIScriptError::warningFlag
, "Security"_ns
, windowID
, uri
);
1537 long nsContentSecurityUtils::ClassifyDownload(
1538 nsIChannel
* aChannel
, const nsAutoCString
& aMimeTypeGuess
) {
1539 MOZ_ASSERT(aChannel
, "IsDownloadAllowed without channel?");
1541 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
1543 nsCOMPtr
<nsIURI
> contentLocation
;
1544 aChannel
->GetURI(getter_AddRefs(contentLocation
));
1546 nsCOMPtr
<nsIPrincipal
> loadingPrincipal
= loadInfo
->GetLoadingPrincipal();
1547 if (!loadingPrincipal
) {
1548 loadingPrincipal
= loadInfo
->TriggeringPrincipal();
1550 // Creating a fake Loadinfo that is just used for the MCB check.
1551 nsCOMPtr
<nsILoadInfo
> secCheckLoadInfo
= new mozilla::net::LoadInfo(
1552 loadingPrincipal
, loadInfo
->TriggeringPrincipal(), nullptr,
1553 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
,
1554 nsIContentPolicy::TYPE_FETCH
);
1556 int16_t decission
= nsIContentPolicy::ACCEPT
;
1557 nsMixedContentBlocker::ShouldLoad(false, // aHadInsecureImageRedirect
1558 contentLocation
, // aContentLocation,
1559 secCheckLoadInfo
, // aLoadinfo
1560 aMimeTypeGuess
, // aMimeGuess,
1561 false, // aReportError
1562 &decission
// aDecision
1564 Telemetry::Accumulate(mozilla::Telemetry::MIXED_CONTENT_DOWNLOADS
,
1565 decission
!= nsIContentPolicy::ACCEPT
);
1567 if (StaticPrefs::dom_block_download_insecure() &&
1568 decission
!= nsIContentPolicy::ACCEPT
) {
1569 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
1571 LogMessageToConsole(httpChannel
, "MixedContentBlockedDownload");
1573 return nsITransfer::DOWNLOAD_POTENTIALLY_UNSAFE
;
1576 if (loadInfo
->TriggeringPrincipal()->IsSystemPrincipal()) {
1577 return nsITransfer::DOWNLOAD_ACCEPTABLE
;
1580 if (!StaticPrefs::dom_block_download_in_sandboxed_iframes()) {
1581 return nsITransfer::DOWNLOAD_ACCEPTABLE
;
1584 uint32_t triggeringFlags
= loadInfo
->GetTriggeringSandboxFlags();
1585 uint32_t currentflags
= loadInfo
->GetSandboxFlags();
1587 if ((triggeringFlags
& SANDBOXED_ALLOW_DOWNLOADS
) ||
1588 (currentflags
& SANDBOXED_ALLOW_DOWNLOADS
)) {
1589 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aChannel
);
1591 LogMessageToConsole(httpChannel
, "IframeSandboxBlockedDownload");
1593 return nsITransfer::DOWNLOAD_FORBIDDEN
;
1596 return nsITransfer::DOWNLOAD_ACCEPTABLE
;