gecko-dev/dom/security/nsContentSecurityUtils.cpp
Tom Ritter 23ba7b6fe3 Bug 1583949 - Add a check for IsEvalAllowed to the worker callpath for eval() r=ckerschb,baku
This patch does several things.  Because Workers aren't on the main thread,
many of the things done are in the name of off main thread access.

1) Changes a parameter in IsEvalAllowed from a nsIPrincipal to a bool.
   We only used the principal to determined if it was the System Principal.
   Principals aren't thread safe and can only be accessed on Main Thread, so
   if we passed a Principal in, we would be in error. Instead only pass in
   the bool which - for workers - comes from a thread-safe location.

2) Separates out the Telemetry Event Recording and sending a message to the
   console into a new function nsContentSecurityUtils::NotifyEvalUsage. (And
   creates a runnable that calls it.)

   We do this because we will need to only call this method on the main thread.

   Telemetry Event Recording has only ever been called on the Main Thread.
   While I possibly-successfully cut it over to happen Off Main Thread (OMT)
   by porting preferences to StaticPrefs, I don't know if there were other
   threading assumptions in the Telemetry Code. So it would be much safer to
   just continue recording Event Telemetry on the main thread.

   Sending a message to the console requires calling GetStringBundleService()
   which requires main thread. I didn't investigate if this could be made
   thread-safe, I just threw it onto the main thread too.

   If, in IsEvalAllowed, we are on the main thread - we call NotifyEvalUsage
   directly. If we are not, we create a runnable which will then call
   NotifyEvalUsage for us on the main thread.

3) Ports allow_eval_with_system_principal and allow_eval_in_parent_process
   from bools to RelaxedAtomicBool - because we now check these prefs OMT.

4) In RuntimeService.cpp, adds the call to IsEvalAllowed.

5) Add resource://gre/modules/workers/require.js to the allowlist of eval
   usage. This was the script that identified this gap in the first place.
   It uses eval (twice) for structural reasons (scope and line number
   massaging.)  The contents of the eval are the result of a request to a
   uri (which may be internal, like resource://). The whole point of this
   is to implement a CommonJS require() api.

   This usage of eval is safe because the only way an attacker can inject
   into it is by either controlling the response of the uri request or
   controlling (or appending to) the argument. If they can do that, they
   are able to inject script into Firefox even if we cut this usage of eval
   over to some other type of safe(r) script loader.

   Bug 1584564 tracks making sure calls to require.js are safe.

6) Adds cld-worker.js to the allowlist. Bug 1584605 is for refactoring that
   eval usage, which is decidedly non-trivial.

7) Does _not_ enforce the eval restrictions for workers. While I've gotten
   try to be green and not throw up any instances of eval-usage by workers,
   it is much safer to deploy this is Telemetry-only mode for Workers for
   a little bit to see if anything pops up from the Nightly population.

   Bug 1584602 is for enforcing the checks.

Differential Revision: https://phabricator.services.mozilla.com/D47480

--HG--
extra : moz-landing-system : lando
2019-10-08 17:31:35 +00:00

617 lines
22 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* A namespace class for static content security utilities. */
#include "nsContentSecurityUtils.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIURI.h"
#include "mozilla/dom/Document.h"
/*
* Performs a Regular Expression match, optionally returning the results.
* This function is not safe to use OMT.
*
* @param aPattern The regex pattern
* @param aString The string to compare against
* @param aOnlyMatch Whether we want match results or only a true/false for
* the match
* @param aMatchResult Out param for whether or not the pattern matched
* @param aRegexResults Out param for the matches of the regex, if requested
* @returns nsresult indicating correct function operation or error
*/
nsresult RegexEval(const nsAString& aPattern, const nsAString& aString,
bool aOnlyMatch, bool& aMatchResult,
nsTArray<nsString>* aRegexResults = nullptr) {
MOZ_ASSERT(NS_IsMainThread());
aMatchResult = false;
AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
AutoDisableJSInterruptCallback disabler(cx);
JSAutoRealm ar(cx, xpc::UnprivilegedJunkScope());
JS::RootedObject regexp(
cx, JS::NewUCRegExpObject(cx, aPattern.BeginReading(), aPattern.Length(),
JS::RegExpFlag::Unicode));
if (!regexp) {
return NS_ERROR_ILLEGAL_VALUE;
}
JS::RootedValue regexResult(cx, JS::NullValue());
size_t index = 0;
if (!JS::ExecuteRegExpNoStatics(cx, regexp, aString.BeginReading(),
aString.Length(), &index, aOnlyMatch,
&regexResult)) {
return NS_ERROR_FAILURE;
}
if (regexResult.isNull()) {
// On no match, ExecuteRegExpNoStatics returns Null
return NS_OK;
}
if (aOnlyMatch) {
// On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean
// true.
MOZ_ASSERT(regexResult.isBoolean() && regexResult.toBoolean());
aMatchResult = true;
return NS_OK;
}
if (aRegexResults == nullptr) {
return NS_ERROR_INVALID_ARG;
}
// Now we know we have a result, and we need to extract it so we can read it.
uint32_t length;
JS::RootedObject regexResultObj(cx, &regexResult.toObject());
if (!JS_GetArrayLength(cx, regexResultObj, &length)) {
return NS_ERROR_NOT_AVAILABLE;
}
MOZ_LOG(sCSMLog, LogLevel::Verbose, ("Regex Matched %i strings", length));
for (uint32_t i = 0; i < length; i++) {
JS::RootedValue element(cx);
if (!JS_GetElement(cx, regexResultObj, i, &element)) {
return NS_ERROR_NO_CONTENT;
}
nsAutoJSString value;
if (!value.init(cx, element)) {
return NS_ERROR_NO_CONTENT;
}
MOZ_LOG(sCSMLog, LogLevel::Verbose,
("Regex Matching: %i: %s", i, NS_ConvertUTF16toUTF8(value).get()));
aRegexResults->AppendElement(value);
}
aMatchResult = true;
return NS_OK;
}
/*
* Telemetry Events extra data only supports 80 characters, so we optimize the
* filename to be smaller and collect more data.
*/
nsString OptimizeFileName(const nsAString& aFileName) {
nsString optimizedName(aFileName);
MOZ_LOG(
sCSMLog, LogLevel::Verbose,
("Optimizing FileName: %s", NS_ConvertUTF16toUTF8(optimizedName).get()));
optimizedName.ReplaceSubstring(NS_LITERAL_STRING(".xpi!"),
NS_LITERAL_STRING("!"));
optimizedName.ReplaceSubstring(NS_LITERAL_STRING("shield.mozilla.org!"),
NS_LITERAL_STRING("s!"));
optimizedName.ReplaceSubstring(NS_LITERAL_STRING("mozilla.org!"),
NS_LITERAL_STRING("m!"));
if (optimizedName.Length() > 80) {
optimizedName.Truncate(80);
}
MOZ_LOG(
sCSMLog, LogLevel::Verbose,
("Optimized FileName: %s", NS_ConvertUTF16toUTF8(optimizedName).get()));
return optimizedName;
}
/*
* FilenameToEvalType takes a fileName and returns a Pair of strings.
* The First entry is a string indicating the type of fileName
* The Second entry is a Maybe<string> that can contain additional details to
* report.
*
* The reason we use strings (instead of an int/enum) is because the Telemetry
* Events API only accepts strings.
*
* Function is a static member of the class to enable gtests.
*/
/* static */
FilenameType nsContentSecurityUtils::FilenameToEvalType(
const nsString& fileName) {
// These are strings because the Telemetry Events API only accepts strings
static NS_NAMED_LITERAL_CSTRING(kChromeURI, "chromeuri");
static NS_NAMED_LITERAL_CSTRING(kResourceURI, "resourceuri");
static NS_NAMED_LITERAL_CSTRING(kSingleString, "singlestring");
static NS_NAMED_LITERAL_CSTRING(kMozillaExtension, "mozillaextension");
static NS_NAMED_LITERAL_CSTRING(kOtherExtension, "otherextension");
static NS_NAMED_LITERAL_CSTRING(kSuspectedUserChromeJS,
"suspectedUserChromeJS");
static NS_NAMED_LITERAL_CSTRING(kOther, "other");
static NS_NAMED_LITERAL_CSTRING(kOtherWorker, "other-on-worker");
static NS_NAMED_LITERAL_CSTRING(kRegexFailure, "regexfailure");
static NS_NAMED_LITERAL_STRING(kUCJSRegex, "(.+).uc.js\\?*[0-9]*$");
static NS_NAMED_LITERAL_STRING(kExtensionRegex, "extensions/(.+)@(.+)!(.+)$");
static NS_NAMED_LITERAL_STRING(kSingleFileRegex, "^[a-zA-Z0-9.?]+$");
// resource:// and chrome://
if (StringBeginsWith(fileName, NS_LITERAL_STRING("chrome://"))) {
return FilenameType(kChromeURI, Some(fileName));
}
if (StringBeginsWith(fileName, NS_LITERAL_STRING("resource://"))) {
return FilenameType(kResourceURI, Some(fileName));
}
if (!NS_IsMainThread()) {
// We can't do Regex matching off the main thread; so just report.
return FilenameType(kOtherWorker, Nothing());
}
// Extension
bool regexMatch;
nsTArray<nsString> regexResults;
nsresult rv = RegexEval(kExtensionRegex, fileName, /* aOnlyMatch = */ false,
regexMatch, &regexResults);
if (NS_FAILED(rv)) {
return FilenameType(kRegexFailure, Nothing());
}
if (regexMatch) {
nsCString type =
StringEndsWith(regexResults[2], NS_LITERAL_STRING("mozilla.org.xpi"))
? kMozillaExtension
: kOtherExtension;
auto& extensionNameAndPath =
Substring(regexResults[0], ArrayLength("extensions/") - 1);
return FilenameType(type, Some(OptimizeFileName(extensionNameAndPath)));
}
// Single File
rv = RegexEval(kSingleFileRegex, fileName, /* aOnlyMatch = */ true,
regexMatch);
if (NS_FAILED(rv)) {
return FilenameType(kRegexFailure, Nothing());
}
if (regexMatch) {
return FilenameType(kSingleString, Some(fileName));
}
// Suspected userChromeJS script
rv = RegexEval(kUCJSRegex, fileName, /* aOnlyMatch = */ true, regexMatch);
if (NS_FAILED(rv)) {
return FilenameType(kRegexFailure, Nothing());
}
if (regexMatch) {
return FilenameType(kSuspectedUserChromeJS, Nothing());
}
return FilenameType(kOther, Nothing());
}
class EvalUsageNotificationRunnable final : public Runnable {
public:
EvalUsageNotificationRunnable(bool aIsSystemPrincipal,
NS_ConvertUTF8toUTF16& aFileNameA,
uint64_t aWindowID, uint32_t aLineNumber,
uint32_t aColumnNumber)
: mozilla::Runnable("EvalUsageNotificationRunnable"),
mIsSystemPrincipal(aIsSystemPrincipal),
mFileNameA(aFileNameA),
mWindowID(aWindowID),
mLineNumber(aLineNumber),
mColumnNumber(aColumnNumber) {}
NS_IMETHOD Run() override {
nsContentSecurityUtils::NotifyEvalUsage(
mIsSystemPrincipal, mFileNameA, mWindowID, mLineNumber, mColumnNumber);
return NS_OK;
}
void Revoke() {}
private:
bool mIsSystemPrincipal;
NS_ConvertUTF8toUTF16 mFileNameA;
uint64_t mWindowID;
uint32_t mLineNumber;
uint32_t mColumnNumber;
};
/* static */
bool nsContentSecurityUtils::IsEvalAllowed(JSContext* cx,
bool aIsSystemPrincipal,
const nsAString& aScript) {
// This allowlist contains files that are permanently allowed to use
// eval()-like functions. It will ideally be restricted to files that are
// exclusively used in testing contexts.
static nsLiteralCString evalAllowlist[] = {
// Test-only third-party library
NS_LITERAL_CSTRING("resource://testing-common/sinon-7.2.7.js"),
// Test-only third-party library
NS_LITERAL_CSTRING("resource://testing-common/ajv-4.1.1.js"),
// Test-only utility
NS_LITERAL_CSTRING("resource://testing-common/content-task.js"),
// Tracked by Bug 1584605
NS_LITERAL_CSTRING("resource:///modules/translation/cld-worker.js"),
// require.js implements a script loader for workers. It uses eval
// to load the script; but injection is only possible in situations
// that you could otherwise control script that gets executed, so
// it is okay to allow eval() as it adds no additional attack surface.
// Bug 1584564 tracks requiring safe usage of require.js
NS_LITERAL_CSTRING("resource://gre/modules/workers/require.js"),
// The Browser Toolbox/Console
NS_LITERAL_CSTRING("debugger"),
};
// We also permit two specific idioms in eval()-like contexts. We'd like to
// elminate these too; but there are in-the-wild Mozilla privileged extensions
// that use them.
static NS_NAMED_LITERAL_STRING(sAllowedEval1, "this");
static NS_NAMED_LITERAL_STRING(sAllowedEval2,
"function anonymous(\n) {\nreturn this\n}");
if (aIsSystemPrincipal &&
StaticPrefs::security_allow_eval_with_system_principal()) {
MOZ_LOG(
sCSMLog, LogLevel::Debug,
("Allowing eval() %s because allowing pref is "
"enabled",
(aIsSystemPrincipal ? "with System Principal" : "in parent process")));
return true;
}
if (XRE_IsE10sParentProcess() &&
StaticPrefs::security_allow_eval_in_parent_process()) {
MOZ_LOG(sCSMLog, LogLevel::Debug,
("Allowing eval() in parent process because allowing pref is "
"enabled"));
return true;
}
if (!aIsSystemPrincipal && !XRE_IsE10sParentProcess()) {
// Usage of eval we are unconcerned with.
return true;
}
// We only perform checks of these two preferences on the Main Thread
// (because a String-based preference checks is only safe on Main Thread.)
// The consequence of this is that if a user is using userChromeJS _and_
// the scripts they use start a worker and that worker uses eval - we will
// enter this function, skip over these pref checks that would normally cause
// us to allow the eval usage - and we will block it.
// While not ideal, we do not officially support userChromeJS, and hopefully
// the usage of workers and eval in workers is even lower that userChromeJS
// usage.
if (NS_IsMainThread()) {
// This preference is a file used for autoconfiguration of Firefox
// by administrators. It has also been (ab)used by the userChromeJS
// project to run legacy-style 'extensions', some of which use eval,
// all of which run in the System Principal context.
nsAutoString jsConfigPref;
Preferences::GetString("general.config.filename", jsConfigPref);
if (!jsConfigPref.IsEmpty()) {
MOZ_LOG(sCSMLog, LogLevel::Debug,
("Allowing eval() %s because of "
"general.config.filename",
(aIsSystemPrincipal ? "with System Principal"
: "in parent process")));
return true;
}
// This preference is better known as userchrome.css which allows
// customization of the Firefox UI. Believe it or not, you can also
// use XBL bindings to get it to run Javascript in the same manner
// as userChromeJS above, so even though 99.9% of people using
// userchrome.css aren't doing that, we're still going to need to
// disable the eval() assertion for them.
if (Preferences::GetBool(
"toolkit.legacyUserProfileCustomizations.stylesheets")) {
MOZ_LOG(sCSMLog, LogLevel::Debug,
("Allowing eval() %s because of "
"toolkit.legacyUserProfileCustomizations.stylesheets",
(aIsSystemPrincipal ? "with System Principal"
: "in parent process")));
return true;
}
}
// We permit these two common idioms to get access to the global JS object
if (!aScript.IsEmpty() &&
(aScript == sAllowedEval1 || aScript == sAllowedEval2)) {
MOZ_LOG(
sCSMLog, LogLevel::Debug,
("Allowing eval() %s because a key string is "
"provided",
(aIsSystemPrincipal ? "with System Principal" : "in parent process")));
return true;
}
// Check the allowlist for the provided filename. getFilename is a helper
// function
nsAutoCString fileName;
uint32_t lineNumber = 0, columnNumber = 0;
JS::AutoFilename rawScriptFilename;
if (JS::DescribeScriptedCaller(cx, &rawScriptFilename, &lineNumber,
&columnNumber)) {
nsDependentCSubstring fileName_(rawScriptFilename.get(),
strlen(rawScriptFilename.get()));
ToLowerCase(fileName_);
// Extract file name alone if scriptFilename contains line number
// separated by multiple space delimiters in few cases.
int32_t fileNameIndex = fileName_.FindChar(' ');
if (fileNameIndex != -1) {
fileName_.SetLength(fileNameIndex);
}
fileName = std::move(fileName_);
} else {
fileName = NS_LITERAL_CSTRING("unknown-file");
}
NS_ConvertUTF8toUTF16 fileNameA(fileName);
for (const nsLiteralCString& allowlistEntry : evalAllowlist) {
if (fileName.Equals(allowlistEntry)) {
MOZ_LOG(sCSMLog, LogLevel::Debug,
("Allowing eval() %s because the containing "
"file is in the allowlist",
(aIsSystemPrincipal ? "with System Principal"
: "in parent process")));
return true;
}
}
// Send Telemetry and Log to the Console
uint64_t windowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
if (NS_IsMainThread()) {
nsContentSecurityUtils::NotifyEvalUsage(aIsSystemPrincipal, fileNameA,
windowID, lineNumber, columnNumber);
} else {
auto runnable = new EvalUsageNotificationRunnable(
aIsSystemPrincipal, fileNameA, windowID, lineNumber, columnNumber);
NS_DispatchToMainThread(runnable);
}
// Log to MOZ_LOG
MOZ_LOG(sCSMLog, LogLevel::Warning,
("Blocking eval() %s from file %s and script "
"provided %s",
(aIsSystemPrincipal ? "with System Principal" : "in parent process"),
fileName.get(), NS_ConvertUTF16toUTF8(aScript).get()));
// Maybe Crash
#ifdef DEBUG
MOZ_CRASH_UNSAFE_PRINTF(
"Blocking eval() %s from file %s and script provided "
"%s",
(aIsSystemPrincipal ? "with System Principal" : "in parent process"),
fileName.get(), NS_ConvertUTF16toUTF8(aScript).get());
#endif
// Do not enforce eval usage blocking on Worker threads; because this is
// new behavior and we want to be conservative so we don't accidently break
// Nightly. Bug 1584602 will enforce things.
return !NS_IsMainThread();
}
/* static */
void nsContentSecurityUtils::NotifyEvalUsage(bool aIsSystemPrincipal,
NS_ConvertUTF8toUTF16& aFileNameA,
uint64_t aWindowID,
uint32_t aLineNumber,
uint32_t aColumnNumber) {
// Send Telemetry
Telemetry::EventID eventType =
aIsSystemPrincipal ? Telemetry::EventID::Security_Evalusage_Systemcontext
: Telemetry::EventID::Security_Evalusage_Parentprocess;
FilenameType fileNameType = FilenameToEvalType(aFileNameA);
mozilla::Maybe<nsTArray<EventExtraEntry>> extra;
if (fileNameType.second().isSome()) {
extra = Some<nsTArray<EventExtraEntry>>({EventExtraEntry{
NS_LITERAL_CSTRING("fileinfo"),
NS_ConvertUTF16toUTF8(fileNameType.second().value())}});
} else {
extra = Nothing();
}
if (!sTelemetryEventEnabled.exchange(true)) {
sTelemetryEventEnabled = true;
Telemetry::SetEventRecordingEnabled(NS_LITERAL_CSTRING("security"), true);
}
Telemetry::RecordEvent(eventType, mozilla::Some(fileNameType.first()), extra);
// Report an error to console
nsCOMPtr<nsIConsoleService> console(
do_GetService(NS_CONSOLESERVICE_CONTRACTID));
if (!console) {
return;
}
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
if (!error) {
return;
}
nsCOMPtr<nsIStringBundle> bundle;
nsCOMPtr<nsIStringBundleService> stringService =
mozilla::services::GetStringBundleService();
if (!stringService) {
return;
}
stringService->CreateBundle(
"chrome://global/locale/security/security.properties",
getter_AddRefs(bundle));
if (!bundle) {
return;
}
nsAutoString message;
AutoTArray<nsString, 1> formatStrings = {aFileNameA};
nsresult rv = bundle->FormatStringFromName("RestrictBrowserEvalUsage",
formatStrings, message);
if (NS_FAILED(rv)) {
return;
}
rv = error->InitWithWindowID(message, aFileNameA, EmptyString(), aLineNumber,
aColumnNumber, nsIScriptError::errorFlag,
"BrowserEvalUsage", aWindowID,
true /* From chrome context */);
if (NS_FAILED(rv)) {
return;
}
console->LogMessage(error);
}
#if defined(DEBUG)
/* static */
void nsContentSecurityUtils::AssertAboutPageHasCSP(Document* aDocument) {
// We want to get to a point where all about: pages ship with a CSP. This
// assertion ensures that we can not deploy new about: pages without a CSP.
// Please note that any about: page should not use inline JS or inline CSS,
// and instead should load JS and CSS from an external file (*.js, *.css)
// which allows us to apply a strong CSP omitting 'unsafe-inline'. Ideally,
// the CSP allows precisely the resources that need to be loaded; but it
// should at least be as strong as:
// <meta http-equiv="Content-Security-Policy" content="default-src chrome:;
// object-src 'none'"/>
// Check if we should skip the assertion
if (Preferences::GetBool("csp.skip_about_page_has_csp_assert")) {
return;
}
// Check if we are loading an about: URI at all
nsCOMPtr<nsIURI> documentURI = aDocument->GetDocumentURI();
if (!documentURI->SchemeIs("about")) {
return;
}
nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
bool foundDefaultSrc = false;
bool foundObjectSrc = false;
bool foundUnsafeEval = false;
bool foundUnsafeInline = false;
if (csp) {
uint32_t policyCount = 0;
csp->GetPolicyCount(&policyCount);
nsAutoString parsedPolicyStr;
for (uint32_t i = 0; i < policyCount; ++i) {
csp->GetPolicyString(i, parsedPolicyStr);
if (parsedPolicyStr.Find("default-src") >= 0) {
foundDefaultSrc = true;
}
if (parsedPolicyStr.Find("object-src 'none'") >= 0) {
foundObjectSrc = true;
}
if (parsedPolicyStr.Find("'unsafe-eval'") >= 0) {
foundUnsafeEval = true;
}
if (parsedPolicyStr.Find("'unsafe-inline'") >= 0) {
foundUnsafeInline = true;
}
}
}
// Check if we should skip the allowlist and assert right away. Please note
// that this pref can and should only be set for automated testing.
if (Preferences::GetBool("csp.skip_about_page_csp_allowlist_and_assert")) {
NS_ASSERTION(foundDefaultSrc, "about: page must have a CSP");
return;
}
nsAutoCString aboutSpec;
documentURI->GetSpec(aboutSpec);
ToLowerCase(aboutSpec);
// This allowlist contains about: pages that are permanently allowed to
// render without a CSP applied.
static nsLiteralCString sAllowedAboutPagesWithNoCSP[] = {
// about:blank is a special about page -> no CSP
NS_LITERAL_CSTRING("about:blank"),
// about:srcdoc is a special about page -> no CSP
NS_LITERAL_CSTRING("about:srcdoc"),
// about:sync-log displays plain text only -> no CSP
NS_LITERAL_CSTRING("about:sync-log"),
// about:printpreview displays plain text only -> no CSP
NS_LITERAL_CSTRING("about:printpreview"),
# if defined(ANDROID)
NS_LITERAL_CSTRING("about:config"),
# endif
};
for (const nsLiteralCString& allowlistEntry : sAllowedAboutPagesWithNoCSP) {
// please note that we perform a substring match here on purpose,
// so we don't have to deal and parse out all the query arguments
// the various about pages rely on.
if (StringBeginsWith(aboutSpec, allowlistEntry)) {
return;
}
}
MOZ_ASSERT(foundDefaultSrc,
"about: page must contain a CSP including default-src");
MOZ_ASSERT(foundObjectSrc,
"about: page must contain a CSP denying object-src");
if (aDocument->IsExtensionPage()) {
// Extensions have two CSP policies applied where the baseline CSP
// includes 'unsafe-eval' and 'unsafe-inline', hence we have to skip
// the 'unsafe-eval' and 'unsafe-inline' assertions for extension
// pages.
return;
}
MOZ_ASSERT(!foundUnsafeEval,
"about: page must not contain a CSP including 'unsafe-eval'");
static nsLiteralCString sLegacyUnsafeInlineAllowList[] = {
// Bug 1579160: Remove 'unsafe-inline' from style-src within
// about:preferences
NS_LITERAL_CSTRING("about:preferences"),
// Bug 1571346: Remove 'unsafe-inline' from style-src within about:addons
NS_LITERAL_CSTRING("about:addons"),
// Bug 1584485: Remove 'unsafe-inline' from style-src within:
// * about:newtab
// * about:welcome
// * about:home
NS_LITERAL_CSTRING("about:newtab"),
NS_LITERAL_CSTRING("about:welcome"),
NS_LITERAL_CSTRING("about:home"),
};
for (const nsLiteralCString& aUnsafeInlineEntry :
sLegacyUnsafeInlineAllowList) {
// please note that we perform a substring match here on purpose,
// so we don't have to deal and parse out all the query arguments
// the various about pages rely on.
if (StringBeginsWith(aboutSpec, aUnsafeInlineEntry)) {
return;
}
}
MOZ_ASSERT(!foundUnsafeInline,
"about: page must not contain a CSP including 'unsafe-inline'");
}
#endif