From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- dom/security/test/gtest/TestCSPParser.cpp | 1148 ++++++++++++++++++++ dom/security/test/gtest/TestFilenameEvalParser.cpp | 453 ++++++++ dom/security/test/gtest/TestSecureContext.cpp | 121 +++ dom/security/test/gtest/TestSmartCrashTrimmer.cpp | 44 + .../test/gtest/TestUnexpectedPrivilegedLoads.cpp | 305 ++++++ dom/security/test/gtest/moz.build | 25 + 6 files changed, 2096 insertions(+) create mode 100644 dom/security/test/gtest/TestCSPParser.cpp create mode 100644 dom/security/test/gtest/TestFilenameEvalParser.cpp create mode 100644 dom/security/test/gtest/TestSecureContext.cpp create mode 100644 dom/security/test/gtest/TestSmartCrashTrimmer.cpp create mode 100644 dom/security/test/gtest/TestUnexpectedPrivilegedLoads.cpp create mode 100644 dom/security/test/gtest/moz.build (limited to 'dom/security/test/gtest') diff --git a/dom/security/test/gtest/TestCSPParser.cpp b/dom/security/test/gtest/TestCSPParser.cpp new file mode 100644 index 0000000000..b8a4e986b6 --- /dev/null +++ b/dom/security/test/gtest/TestCSPParser.cpp @@ -0,0 +1,1148 @@ +/* -*- 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/. */ + +#include "gtest/gtest.h" + +#include +#include + +#include "nsIContentSecurityPolicy.h" +#include "nsNetUtil.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsComponentManagerUtils.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsStringFwd.h" + +/* + * Testing the parser is non trivial, especially since we can not call + * parser functionality directly in compiled code tests. + * All the tests (except the fuzzy tests at the end) follow the same schemata: + * a) create an nsIContentSecurityPolicy object + * b) set the selfURI in SetRequestContextWithPrincipal + * c) append one or more policies by calling AppendPolicy + * d) check if the policy count is correct by calling GetPolicyCount + * e) compare the result of the policy with the expected output + * using the struct PolicyTest; + * + * In general we test: + * a) policies that the parser should accept + * b) policies that the parser should reject + * c) policies that are randomly generated (fuzzy tests) + * + * Please note that fuzzy tests are *DISABLED* by default and shold only + * be run *OFFLINE* whenever code in nsCSPParser changes. + * To run fuzzy tests, flip RUN_OFFLINE_TESTS to 1. + * + */ + +#define RUN_OFFLINE_TESTS 0 + +/* + * Offline tests are separated in three different groups: + * * TestFuzzyPolicies - complete random ASCII input + * * TestFuzzyPoliciesIncDir - a directory name followed by random ASCII + * * TestFuzzyPoliciesIncDirLimASCII - a directory name followed by limited + * ASCII which represents more likely user input. + * + * We run each of this categories |kFuzzyRuns| times. + */ + +#if RUN_OFFLINE_TESTS +static const uint32_t kFuzzyRuns = 10000; +#endif + +// For fuzzy testing we actually do not care about the output, +// we just want to make sure that the parser can handle random +// input, therefore we use kFuzzyExpectedPolicyCount to return early. +static const uint32_t kFuzzyExpectedPolicyCount = 111; + +static const uint32_t kMaxPolicyLength = 96; + +struct PolicyTest { + char policy[kMaxPolicyLength]; + char expectedResult[kMaxPolicyLength]; +}; + +nsresult runTest( + uint32_t aExpectedPolicyCount, // this should be 0 for policies which + // should fail to parse + const char* aPolicy, const char* aExpectedResult) { + nsresult rv; + + // we init the csp with http://www.selfuri.com + nsCOMPtr selfURI; + rv = NS_NewURI(getter_AddRefs(selfURI), "http://www.selfuri.com"); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr selfURIPrincipal; + mozilla::OriginAttributes attrs; + selfURIPrincipal = + mozilla::BasePrincipal::CreateContentPrincipal(selfURI, attrs); + NS_ENSURE_TRUE(selfURIPrincipal, NS_ERROR_FAILURE); + + // create a CSP object + nsCOMPtr csp = + do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // for testing the parser we only need to set a principal which is needed + // to translate the keyword 'self' into an actual URI. + rv = + csp->SetRequestContextWithPrincipal(selfURIPrincipal, selfURI, u""_ns, 0); + NS_ENSURE_SUCCESS(rv, rv); + + // append a policy + nsString policyStr; + policyStr.AssignASCII(aPolicy); + rv = csp->AppendPolicy(policyStr, false, false); + NS_ENSURE_SUCCESS(rv, rv); + + // when executing fuzzy tests we do not care about the actual output + // of the parser, we just want to make sure that the parser is not crashing. + if (aExpectedPolicyCount == kFuzzyExpectedPolicyCount) { + return NS_OK; + } + + // verify that the expected number of policies exists + uint32_t actualPolicyCount; + rv = csp->GetPolicyCount(&actualPolicyCount); + NS_ENSURE_SUCCESS(rv, rv); + if (actualPolicyCount != aExpectedPolicyCount) { + EXPECT_TRUE(false) + << "Actual policy count not equal to expected policy count (" + << actualPolicyCount << " != " << aExpectedPolicyCount + << ") for policy: " << aPolicy; + return NS_ERROR_UNEXPECTED; + } + + // if the expected policy count is 0, we can return, because + // we can not compare any output anyway. Used when parsing + // errornous policies. + if (aExpectedPolicyCount == 0) { + return NS_OK; + } + + // compare the parsed policy against the expected result + nsString parsedPolicyStr; + // checking policy at index 0, which is the one what we appended. + rv = csp->GetPolicyString(0, parsedPolicyStr); + NS_ENSURE_SUCCESS(rv, rv); + + if (!NS_ConvertUTF16toUTF8(parsedPolicyStr).EqualsASCII(aExpectedResult)) { + EXPECT_TRUE(false) << "Actual policy does not match expected policy (" + << NS_ConvertUTF16toUTF8(parsedPolicyStr).get() + << " != " << aExpectedResult << ")"; + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +// ============================= run Tests ======================== + +nsresult runTestSuite(const PolicyTest* aPolicies, uint32_t aPolicyCount, + uint32_t aExpectedPolicyCount) { + nsresult rv; + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + // Add prefs you need to set to parse CSP here, see comments for example + // bool examplePref = false; + if (prefs) { + // prefs->GetBoolPref("security.csp.examplePref", &examplePref); + // prefs->SetBoolPref("security.csp.examplePref", true); + } + + for (uint32_t i = 0; i < aPolicyCount; i++) { + rv = runTest(aExpectedPolicyCount, aPolicies[i].policy, + aPolicies[i].expectedResult); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (prefs) { + // prefs->SetBoolPref("security.csp.examplePref", examplePref); + } + + return NS_OK; +} + +// ============================= TestDirectives ======================== + +TEST(CSPParser, Directives) +{ + static const PolicyTest policies[] = { + // clang-format off + { "connect-src xn--mnchen-3ya.de", + "connect-src http://xn--mnchen-3ya.de"}, + { "default-src http://www.example.com", + "default-src http://www.example.com" }, + { "script-src http://www.example.com", + "script-src http://www.example.com" }, + { "object-src http://www.example.com", + "object-src http://www.example.com" }, + { "style-src http://www.example.com", + "style-src http://www.example.com" }, + { "img-src http://www.example.com", + "img-src http://www.example.com" }, + { "media-src http://www.example.com", + "media-src http://www.example.com" }, + { "frame-src http://www.example.com", + "frame-src http://www.example.com" }, + { "font-src http://www.example.com", + "font-src http://www.example.com" }, + { "connect-src http://www.example.com", + "connect-src http://www.example.com" }, + { "report-uri http://www.example.com", + "report-uri http://www.example.com/" }, + { "script-src 'nonce-correctscriptnonce'", + "script-src 'nonce-correctscriptnonce'" }, + { "script-src 'nonce-a'", + "script-src 'nonce-a'" }, + { "script-src 'sha256-a'", + "script-src 'sha256-a'" }, + { "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='", + "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" }, + { "script-src 'nonce-foo' 'unsafe-inline' ", + "script-src 'nonce-foo' 'unsafe-inline'" }, + { "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https: ", + "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:" }, + { "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' 'report-sample' https: ", + "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' 'report-sample' https:" }, + { "default-src 'sha256-siVR8' 'strict-dynamic' 'unsafe-inline' https: ", + "default-src 'sha256-siVR8' 'strict-dynamic' 'unsafe-inline' https:" }, + { "worker-src https://example.com", + "worker-src https://example.com" }, + { "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com", + "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com" }, + { "script-src 'unsafe-allow-redirects' http://example.com", + "script-src http://example.com"}, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// ============================= TestKeywords ======================== + +TEST(CSPParser, Keywords) +{ + static const PolicyTest policies[] = { + // clang-format off + { "script-src 'self'", + "script-src 'self'" }, + { "script-src 'unsafe-inline'", + "script-src 'unsafe-inline'" }, + { "script-src 'unsafe-eval'", + "script-src 'unsafe-eval'" }, + { "script-src 'unsafe-inline' 'unsafe-eval'", + "script-src 'unsafe-inline' 'unsafe-eval'" }, + { "script-src 'none'", + "script-src 'none'" }, + { "script-src 'wasm-unsafe-eval'", + "script-src 'wasm-unsafe-eval'" }, + { "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'", + "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'" }, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// =================== TestIgnoreUpperLowerCasePolicies ============== + +TEST(CSPParser, IgnoreUpperLowerCasePolicies) +{ + static const PolicyTest policies[] = { + // clang-format off + { "script-src 'SELF'", + "script-src 'self'" }, + { "sCriPt-src 'Unsafe-Inline'", + "script-src 'unsafe-inline'" }, + { "SCRIPT-src 'unsafe-eval'", + "script-src 'unsafe-eval'" }, + { "default-SRC 'unsafe-inline' 'unsafe-eval'", + "default-src 'unsafe-inline' 'unsafe-eval'" }, + { "script-src 'NoNe'", + "script-src 'none'" }, + { "img-sRc 'noNe'; scrIpt-src 'unsafe-EVAL' 'UNSAFE-inline'; deFAULT-src 'Self'", + "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'" }, + { "default-src HTTP://www.example.com", + "default-src http://www.example.com" }, + { "default-src HTTP://WWW.EXAMPLE.COM", + "default-src http://www.example.com" }, + { "default-src HTTPS://*.example.COM", + "default-src https://*.example.com" }, + { "script-src 'none' test.com;", + "script-src http://test.com" }, + { "script-src 'NoNCE-correctscriptnonce'", + "script-src 'nonce-correctscriptnonce'" }, + { "script-src 'NoncE-NONCENEEDSTOBEUPPERCASE'", + "script-src 'nonce-NONCENEEDSTOBEUPPERCASE'" }, + { "script-src 'SHA256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='", + "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" }, + { "upgrade-INSECURE-requests", + "upgrade-insecure-requests" }, + { "sanDBox alloW-foRMs", + "sandbox allow-forms"}, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// ========================= TestPaths =============================== + +TEST(CSPParser, Paths) +{ + static const PolicyTest policies[] = { + // clang-format off + { "script-src http://www.example.com", + "script-src http://www.example.com" }, + { "script-src http://www.example.com/", + "script-src http://www.example.com/" }, + { "script-src http://www.example.com/path-1", + "script-src http://www.example.com/path-1" }, + { "script-src http://www.example.com/path-1/", + "script-src http://www.example.com/path-1/" }, + { "script-src http://www.example.com/path-1/path_2", + "script-src http://www.example.com/path-1/path_2" }, + { "script-src http://www.example.com/path-1/path_2/", + "script-src http://www.example.com/path-1/path_2/" }, + { "script-src http://www.example.com/path-1/path_2/file.js", + "script-src http://www.example.com/path-1/path_2/file.js" }, + { "script-src http://www.example.com/path-1/path_2/file_1.js", + "script-src http://www.example.com/path-1/path_2/file_1.js" }, + { "script-src http://www.example.com/path-1/path_2/file-2.js", + "script-src http://www.example.com/path-1/path_2/file-2.js" }, + { "script-src http://www.example.com/path-1/path_2/f.js", + "script-src http://www.example.com/path-1/path_2/f.js" }, + { "script-src http://www.example.com:88", + "script-src http://www.example.com:88" }, + { "script-src http://www.example.com:88/", + "script-src http://www.example.com:88/" }, + { "script-src http://www.example.com:88/path-1", + "script-src http://www.example.com:88/path-1" }, + { "script-src http://www.example.com:88/path-1/", + "script-src http://www.example.com:88/path-1/" }, + { "script-src http://www.example.com:88/path-1/path_2", + "script-src http://www.example.com:88/path-1/path_2" }, + { "script-src http://www.example.com:88/path-1/path_2/", + "script-src http://www.example.com:88/path-1/path_2/" }, + { "script-src http://www.example.com:88/path-1/path_2/file.js", + "script-src http://www.example.com:88/path-1/path_2/file.js" }, + { "script-src http://www.example.com:*", + "script-src http://www.example.com:*" }, + { "script-src http://www.example.com:*/", + "script-src http://www.example.com:*/" }, + { "script-src http://www.example.com:*/path-1", + "script-src http://www.example.com:*/path-1" }, + { "script-src http://www.example.com:*/path-1/", + "script-src http://www.example.com:*/path-1/" }, + { "script-src http://www.example.com:*/path-1/path_2", + "script-src http://www.example.com:*/path-1/path_2" }, + { "script-src http://www.example.com:*/path-1/path_2/", + "script-src http://www.example.com:*/path-1/path_2/" }, + { "script-src http://www.example.com:*/path-1/path_2/file.js", + "script-src http://www.example.com:*/path-1/path_2/file.js" }, + { "script-src http://www.example.com#foo", + "script-src http://www.example.com" }, + { "script-src http://www.example.com?foo=bar", + "script-src http://www.example.com" }, + { "script-src http://www.example.com:8888#foo", + "script-src http://www.example.com:8888" }, + { "script-src http://www.example.com:8888?foo", + "script-src http://www.example.com:8888" }, + { "script-src http://www.example.com/#foo", + "script-src http://www.example.com/" }, + { "script-src http://www.example.com/?foo", + "script-src http://www.example.com/" }, + { "script-src http://www.example.com/path-1/file.js#foo", + "script-src http://www.example.com/path-1/file.js" }, + { "script-src http://www.example.com/path-1/file.js?foo", + "script-src http://www.example.com/path-1/file.js" }, + { "script-src http://www.example.com/path-1/file.js?foo#bar", + "script-src http://www.example.com/path-1/file.js" }, + { "report-uri http://www.example.com/", + "report-uri http://www.example.com/" }, + { "report-uri http://www.example.com:8888/asdf", + "report-uri http://www.example.com:8888/asdf" }, + { "report-uri http://www.example.com:8888/path_1/path_2", + "report-uri http://www.example.com:8888/path_1/path_2" }, + { "report-uri http://www.example.com:8888/path_1/path_2/report.sjs&301", + "report-uri http://www.example.com:8888/path_1/path_2/report.sjs&301" }, + { "report-uri /examplepath", + "report-uri http://www.selfuri.com/examplepath" }, + { "connect-src http://www.example.com/foo%3Bsessionid=12%2C34", + "connect-src http://www.example.com/foo;sessionid=12,34" }, + { "connect-src http://www.example.com/foo%3bsessionid=12%2c34", + "connect-src http://www.example.com/foo;sessionid=12,34" }, + { "connect-src http://test.com/pathIncludingAz19-._~!$&'()*+=:@", + "connect-src http://test.com/pathIncludingAz19-._~!$&'()*+=:@" }, + { "script-src http://www.example.com:88/.js", + "script-src http://www.example.com:88/.js" }, + { "script-src https://foo.com/_abc/abc_/_/_a_b_c_", + "script-src https://foo.com/_abc/abc_/_/_a_b_c_" } + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// ======================== TestSimplePolicies ======================= + +TEST(CSPParser, SimplePolicies) +{ + static const PolicyTest policies[] = { + // clang-format off + { "frame-src intent:", + "frame-src intent:" }, + { "frame-src intent://host.name", + "frame-src intent://host.name" }, + { "frame-src intent://my.host.link/", + "frame-src intent://my.host.link/" }, + { "default-src *", + "default-src *" }, + { "default-src https:", + "default-src https:" }, + { "default-src https://*", + "default-src https://*" }, + { "default-src *:*", + "default-src http://*:*" }, + { "default-src *:80", + "default-src http://*:80" }, + { "default-src http://*:80", + "default-src http://*:80" }, + { "default-src javascript:", + "default-src javascript:" }, + { "default-src data:", + "default-src data:" }, + { "script-src 'unsafe-eval' 'unsafe-inline' http://www.example.com", + "script-src 'unsafe-eval' 'unsafe-inline' http://www.example.com" }, + { "object-src 'self'", + "object-src 'self'" }, + { "style-src http://www.example.com 'self'", + "style-src http://www.example.com 'self'" }, + { "media-src http://www.example.com http://www.test.com", + "media-src http://www.example.com http://www.test.com" }, + { "connect-src http://www.test.com example.com *.other.com;", + "connect-src http://www.test.com http://example.com http://*.other.com"}, + { "connect-src example.com *.other.com", + "connect-src http://example.com http://*.other.com"}, + { "style-src *.other.com example.com", + "style-src http://*.other.com http://example.com"}, + { "default-src 'self'; img-src *;", + "default-src 'self'; img-src *" }, + { "object-src media1.example.com media2.example.com *.cdn.example.com;", + "object-src http://media1.example.com http://media2.example.com http://*.cdn.example.com" }, + { "script-src trustedscripts.example.com", + "script-src http://trustedscripts.example.com" }, + { "script-src 'self' ; default-src trustedscripts.example.com", + "script-src 'self'; default-src http://trustedscripts.example.com" }, + { "default-src 'none'; report-uri http://localhost:49938/test", + "default-src 'none'; report-uri http://localhost:49938/test" }, + { " ; default-src abc", + "default-src http://abc" }, + { " ; ; ; ; default-src abc ; ; ; ;", + "default-src http://abc" }, + { "script-src 'none' 'none' 'none';", + "script-src 'none'" }, + { "script-src http://www.example.com/path-1//", + "script-src http://www.example.com/path-1//" }, + { "script-src http://www.example.com/path-1//path_2", + "script-src http://www.example.com/path-1//path_2" }, + { "default-src 127.0.0.1", + "default-src http://127.0.0.1" }, + { "default-src 127.0.0.1:*", + "default-src http://127.0.0.1:*" }, + { "default-src -; ", + "default-src http://-" }, + { "script-src 1", + "script-src http://1" }, + { "upgrade-insecure-requests", + "upgrade-insecure-requests" }, + { "upgrade-insecure-requests https:", + "upgrade-insecure-requests" }, + { "sandbox allow-scripts allow-forms ", + "sandbox allow-scripts allow-forms" }, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// =================== TestPoliciesWithInvalidSrc ==================== + +TEST(CSPParser, PoliciesWithInvalidSrc) +{ + static const PolicyTest policies[] = { + // clang-format off + { "script-src 'self'; SCRIPT-SRC http://www.example.com", + "script-src 'self'" }, + { "script-src 'none' test.com; script-src example.com", + "script-src http://test.com" }, + { "default-src **", + "default-src 'none'" }, + { "default-src 'self", + "default-src 'none'" }, + { "default-src 'unsafe-inlin' ", + "default-src 'none'" }, + { "default-src */", + "default-src 'none'" }, + { "default-src", + "default-src 'none'" }, + { "default-src 'unsafe-inlin' ", + "default-src 'none'" }, + { "default-src :88", + "default-src 'none'" }, + { "script-src abc::::::88", + "script-src 'none'" }, + { "script-src *.*:*", + "script-src 'none'" }, + { "img-src *::88", + "img-src 'none'" }, + { "object-src http://localhost:", + "object-src 'none'" }, + { "script-src test..com", + "script-src 'none'" }, + { "script-src sub1.sub2.example+", + "script-src 'none'" }, + { "script-src http://www.example.com//", + "script-src 'none'" }, + { "script-src http://www.example.com:88path-1/", + "script-src 'none'" }, + { "script-src http://www.example.com:88//", + "script-src 'none'" }, + { "script-src http://www.example.com:88//path-1", + "script-src 'none'" }, + { "script-src http://www.example.com:88//path-1", + "script-src 'none'" }, + { "script-src http://www.example.com:88.js", + "script-src 'none'" }, + { "script-src http://www.example.com:*.js", + "script-src 'none'" }, + { "script-src http://www.example.com:*.", + "script-src 'none'" }, + { "script-src 'nonce-{invalid}'", + "script-src 'none'" }, + { "script-src 'sha256-{invalid}'", + "script-src 'none'" }, + { "script-src 'nonce-in$valid'", + "script-src 'none'" }, + { "script-src 'sha256-in$valid'", + "script-src 'none'" }, + { "script-src 'nonce-invalid==='", + "script-src 'none'" }, + { "script-src 'sha256-invalid==='", + "script-src 'none'" }, + { "script-src 'nonce-==='", + "script-src 'none'" }, + { "script-src 'sha256-==='", + "script-src 'none'" }, + { "script-src 'nonce-=='", + "script-src 'none'" }, + { "script-src 'sha256-=='", + "script-src 'none'" }, + { "script-src 'nonce-='", + "script-src 'none'" }, + { "script-src 'sha256-='", + "script-src 'none'" }, + { "script-src 'nonce-'", + "script-src 'none'" }, + { "script-src 'sha256-'", + "script-src 'none'" }, + { "connect-src http://www.example.com/foo%zz;", + "connect-src 'none'" }, + { "script-src https://foo.com/%$", + "script-src 'none'" }, + { "sandbox foo", + "sandbox"}, + // clang-format on + }; + + // amount of tests - 1, because the latest should be ignored. + uint32_t policyCount = (sizeof(policies) / sizeof(PolicyTest)) - 1; + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// ============================= TestBadPolicies ======================= + +TEST(CSPParser, BadPolicies) +{ + static const PolicyTest policies[] = { + // clang-format off + { "script-sr 'self", "" }, + { "", "" }, + { "; ; ; ; ; ; ;", "" }, + { "defaut-src asdf", "" }, + { "default-src: aaa", "" }, + { "asdf http://test.com", ""}, + { "report-uri", ""}, + { "report-uri http://:foo", ""}, + { "require-sri-for", ""}, + { "require-sri-for style", ""}, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 0)); +} + +// ======================= TestGoodGeneratedPolicies ================= + +TEST(CSPParser, GoodGeneratedPolicies) +{ + static const PolicyTest policies[] = { + // clang-format off + { "default-src 'self'; img-src *", + "default-src 'self'; img-src *" }, + { "report-uri /policy", + "report-uri http://www.selfuri.com/policy"}, + { "img-src *", + "img-src *" }, + { "media-src foo.bar", + "media-src http://foo.bar" }, + { "frame-src *.bar", + "frame-src http://*.bar" }, + { "font-src com", + "font-src http://com" }, + { "connect-src f00b4r.com", + "connect-src http://f00b4r.com" }, + { "script-src *.a.b.c", + "script-src http://*.a.b.c" }, + { "object-src *.b.c", + "object-src http://*.b.c" }, + { "style-src a.b.c", + "style-src http://a.b.c" }, + { "img-src a.com", + "img-src http://a.com" }, + { "media-src http://abc.com", + "media-src http://abc.com" }, + { "frame-src a2-c.com", + "frame-src http://a2-c.com" }, + { "font-src https://a.com", + "font-src https://a.com" }, + { "connect-src *.a.com", + "connect-src http://*.a.com" }, + { "default-src a.com:23", + "default-src http://a.com:23" }, + { "script-src https://a.com:200", + "script-src https://a.com:200" }, + { "object-src data:", + "object-src data:" }, + { "style-src javascript:", + "style-src javascript:" }, + { "frame-src https://foobar.com:443", + "frame-src https://foobar.com:443" }, + { "font-src https://a.com:443", + "font-src https://a.com:443" }, + { "connect-src http://a.com:80", + "connect-src http://a.com:80" }, + { "default-src http://foobar.com", + "default-src http://foobar.com" }, + { "script-src https://foobar.com", + "script-src https://foobar.com" }, + { "style-src 'none'", + "style-src 'none'" }, + { "img-src foo.bar:21 https://ras.bar", + "img-src http://foo.bar:21 https://ras.bar" }, + { "media-src http://foo.bar:21 https://ras.bar:443", + "media-src http://foo.bar:21 https://ras.bar:443" }, + { "frame-src http://self.com:80", + "frame-src http://self.com:80" }, + { "font-src http://self.com", + "font-src http://self.com" }, + { "connect-src https://foo.com http://bar.com:88", + "connect-src https://foo.com http://bar.com:88" }, + { "default-src * https://bar.com 'none'", + "default-src * https://bar.com" }, + { "script-src *.foo.com", + "script-src http://*.foo.com" }, + { "object-src http://b.com", + "object-src http://b.com" }, + { "style-src http://bar.com:88", + "style-src http://bar.com:88" }, + { "img-src https://bar.com:88", + "img-src https://bar.com:88" }, + { "media-src http://bar.com:443", + "media-src http://bar.com:443" }, + { "frame-src https://foo.com:88", + "frame-src https://foo.com:88" }, + { "font-src http://foo.com", + "font-src http://foo.com" }, + { "connect-src http://x.com:23", + "connect-src http://x.com:23" }, + { "default-src http://barbaz.com", + "default-src http://barbaz.com" }, + { "script-src http://somerandom.foo.com", + "script-src http://somerandom.foo.com" }, + { "default-src *", + "default-src *" }, + { "style-src http://bar.com:22", + "style-src http://bar.com:22" }, + { "img-src https://foo.com:443", + "img-src https://foo.com:443" }, + { "script-src https://foo.com; ", + "script-src https://foo.com" }, + { "img-src bar.com:*", + "img-src http://bar.com:*" }, + { "font-src https://foo.com:400", + "font-src https://foo.com:400" }, + { "connect-src http://bar.com:400", + "connect-src http://bar.com:400" }, + { "default-src http://evil.com", + "default-src http://evil.com" }, + { "script-src https://evil.com:100", + "script-src https://evil.com:100" }, + { "default-src bar.com; script-src https://foo.com", + "default-src http://bar.com; script-src https://foo.com" }, + { "default-src 'self'; script-src 'self' https://*:*", + "default-src 'self'; script-src 'self' https://*:*" }, + { "img-src http://self.com:34", + "img-src http://self.com:34" }, + { "media-src http://subd.self.com:34", + "media-src http://subd.self.com:34" }, + { "default-src 'none'", + "default-src 'none'" }, + { "connect-src http://self", + "connect-src http://self" }, + { "default-src http://foo", + "default-src http://foo" }, + { "script-src http://foo:80", + "script-src http://foo:80" }, + { "object-src http://bar", + "object-src http://bar" }, + { "style-src http://three:80", + "style-src http://three:80" }, + { "img-src https://foo:400", + "img-src https://foo:400" }, + { "media-src https://self:34", + "media-src https://self:34" }, + { "frame-src https://bar", + "frame-src https://bar" }, + { "font-src http://three:81", + "font-src http://three:81" }, + { "connect-src https://three:81", + "connect-src https://three:81" }, + { "script-src http://self.com:80/foo", + "script-src http://self.com:80/foo" }, + { "object-src http://self.com/foo", + "object-src http://self.com/foo" }, + { "report-uri /report.py", + "report-uri http://www.selfuri.com/report.py"}, + { "img-src http://foo.org:34/report.py", + "img-src http://foo.org:34/report.py" }, + { "media-src foo/bar/report.py", + "media-src http://foo/bar/report.py" }, + { "report-uri /", + "report-uri http://www.selfuri.com/"}, + { "font-src https://self.com/report.py", + "font-src https://self.com/report.py" }, + { "connect-src https://foo.com/report.py", + "connect-src https://foo.com/report.py" }, + { "default-src *; report-uri http://www.reporturi.com/", + "default-src *; report-uri http://www.reporturi.com/" }, + { "default-src http://first.com", + "default-src http://first.com" }, + { "script-src http://second.com", + "script-src http://second.com" }, + { "object-src http://third.com", + "object-src http://third.com" }, + { "style-src https://foobar.com:4443", + "style-src https://foobar.com:4443" }, + { "img-src http://foobar.com:4443", + "img-src http://foobar.com:4443" }, + { "media-src bar.com", + "media-src http://bar.com" }, + { "frame-src http://bar.com", + "frame-src http://bar.com" }, + { "font-src http://self.com/", + "font-src http://self.com/" }, + { "script-src 'self'", + "script-src 'self'" }, + { "default-src http://self.com/foo.png", + "default-src http://self.com/foo.png" }, + { "script-src http://self.com/foo.js", + "script-src http://self.com/foo.js" }, + { "object-src http://bar.com/foo.js", + "object-src http://bar.com/foo.js" }, + { "style-src http://FOO.COM", + "style-src http://foo.com" }, + { "img-src HTTP", + "img-src http://http" }, + { "media-src http", + "media-src http://http" }, + { "frame-src 'SELF'", + "frame-src 'self'" }, + { "DEFAULT-src 'self';", + "default-src 'self'" }, + { "default-src 'self' http://FOO.COM", + "default-src 'self' http://foo.com" }, + { "default-src 'self' HTTP://foo.com", + "default-src 'self' http://foo.com" }, + { "default-src 'NONE'", + "default-src 'none'" }, + { "script-src policy-uri ", + "script-src http://policy-uri" }, + { "img-src 'self'; ", + "img-src 'self'" }, + { "frame-ancestors foo-bar.com", + "frame-ancestors http://foo-bar.com" }, + { "frame-ancestors http://a.com", + "frame-ancestors http://a.com" }, + { "frame-ancestors 'self'", + "frame-ancestors 'self'" }, + { "frame-ancestors http://self.com:88", + "frame-ancestors http://self.com:88" }, + { "frame-ancestors http://a.b.c.d.e.f.g.h.i.j.k.l.x.com", + "frame-ancestors http://a.b.c.d.e.f.g.h.i.j.k.l.x.com" }, + { "frame-ancestors https://self.com:34", + "frame-ancestors https://self.com:34" }, + { "frame-ancestors http://sampleuser:samplepass@example.com", + "frame-ancestors 'none'" }, + { "default-src 'none'; frame-ancestors 'self'", + "default-src 'none'; frame-ancestors 'self'" }, + { "frame-ancestors http://self:80", + "frame-ancestors http://self:80" }, + { "frame-ancestors http://self.com/bar", + "frame-ancestors http://self.com/bar" }, + { "default-src 'self'; frame-ancestors 'self'", + "default-src 'self'; frame-ancestors 'self'" }, + { "frame-ancestors http://bar.com/foo.png", + "frame-ancestors http://bar.com/foo.png" }, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// ==================== TestBadGeneratedPolicies ==================== + +TEST(CSPParser, BadGeneratedPolicies) +{ + static const PolicyTest policies[] = { + // clang-format off + { "foo.*.bar", ""}, + { "foo!bar.com", ""}, + { "x.*.a.com", ""}, + { "a#2-c.com", ""}, + { "http://foo.com:bar.com:23", ""}, + { "f!oo.bar", ""}, + { "ht!ps://f-oo.bar", ""}, + { "https://f-oo.bar:3f", ""}, + { "**", ""}, + { "*a", ""}, + { "http://username:password@self.com/foo", ""}, + { "http://other:pass1@self.com/foo", ""}, + { "http://user1:pass1@self.com/foo", ""}, + { "http://username:password@self.com/bar", ""}, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 0)); +} + +// ============ TestGoodGeneratedPoliciesForPathHandling ============= + +TEST(CSPParser, GoodGeneratedPoliciesForPathHandling) +{ + // Once bug 808292 (Implement path-level host-source matching to CSP) + // lands we have to update the expected output to include the parsed path + + static const PolicyTest policies[] = { + // clang-format off + { "img-src http://test1.example.com", + "img-src http://test1.example.com" }, + { "img-src http://test1.example.com/", + "img-src http://test1.example.com/" }, + { "img-src http://test1.example.com/path-1", + "img-src http://test1.example.com/path-1" }, + { "img-src http://test1.example.com/path-1/", + "img-src http://test1.example.com/path-1/" }, + { "img-src http://test1.example.com/path-1/path_2/", + "img-src http://test1.example.com/path-1/path_2/" }, + { "img-src http://test1.example.com/path-1/path_2/file.js", + "img-src http://test1.example.com/path-1/path_2/file.js" }, + { "img-src http://test1.example.com/path-1/path_2/file_1.js", + "img-src http://test1.example.com/path-1/path_2/file_1.js" }, + { "img-src http://test1.example.com/path-1/path_2/file-2.js", + "img-src http://test1.example.com/path-1/path_2/file-2.js" }, + { "img-src http://test1.example.com/path-1/path_2/f.js", + "img-src http://test1.example.com/path-1/path_2/f.js" }, + { "img-src http://test1.example.com/path-1/path_2/f.oo.js", + "img-src http://test1.example.com/path-1/path_2/f.oo.js" }, + { "img-src test1.example.com", + "img-src http://test1.example.com" }, + { "img-src test1.example.com/", + "img-src http://test1.example.com/" }, + { "img-src test1.example.com/path-1", + "img-src http://test1.example.com/path-1" }, + { "img-src test1.example.com/path-1/", + "img-src http://test1.example.com/path-1/" }, + { "img-src test1.example.com/path-1/path_2/", + "img-src http://test1.example.com/path-1/path_2/" }, + { "img-src test1.example.com/path-1/path_2/file.js", + "img-src http://test1.example.com/path-1/path_2/file.js" }, + { "img-src test1.example.com/path-1/path_2/file_1.js", + "img-src http://test1.example.com/path-1/path_2/file_1.js" }, + { "img-src test1.example.com/path-1/path_2/file-2.js", + "img-src http://test1.example.com/path-1/path_2/file-2.js" }, + { "img-src test1.example.com/path-1/path_2/f.js", + "img-src http://test1.example.com/path-1/path_2/f.js" }, + { "img-src test1.example.com/path-1/path_2/f.oo.js", + "img-src http://test1.example.com/path-1/path_2/f.oo.js" }, + { "img-src *.example.com", + "img-src http://*.example.com" }, + { "img-src *.example.com/", + "img-src http://*.example.com/" }, + { "img-src *.example.com/path-1", + "img-src http://*.example.com/path-1" }, + { "img-src *.example.com/path-1/", + "img-src http://*.example.com/path-1/" }, + { "img-src *.example.com/path-1/path_2/", + "img-src http://*.example.com/path-1/path_2/" }, + { "img-src *.example.com/path-1/path_2/file.js", + "img-src http://*.example.com/path-1/path_2/file.js" }, + { "img-src *.example.com/path-1/path_2/file_1.js", + "img-src http://*.example.com/path-1/path_2/file_1.js" }, + { "img-src *.example.com/path-1/path_2/file-2.js", + "img-src http://*.example.com/path-1/path_2/file-2.js" }, + { "img-src *.example.com/path-1/path_2/f.js", + "img-src http://*.example.com/path-1/path_2/f.js" }, + { "img-src *.example.com/path-1/path_2/f.oo.js", + "img-src http://*.example.com/path-1/path_2/f.oo.js" }, + { "img-src test1.example.com:80", + "img-src http://test1.example.com:80" }, + { "img-src test1.example.com:80/", + "img-src http://test1.example.com:80/" }, + { "img-src test1.example.com:80/path-1", + "img-src http://test1.example.com:80/path-1" }, + { "img-src test1.example.com:80/path-1/", + "img-src http://test1.example.com:80/path-1/" }, + { "img-src test1.example.com:80/path-1/path_2", + "img-src http://test1.example.com:80/path-1/path_2" }, + { "img-src test1.example.com:80/path-1/path_2/", + "img-src http://test1.example.com:80/path-1/path_2/" }, + { "img-src test1.example.com:80/path-1/path_2/file.js", + "img-src http://test1.example.com:80/path-1/path_2/file.js" }, + { "img-src test1.example.com:80/path-1/path_2/f.ile.js", + "img-src http://test1.example.com:80/path-1/path_2/f.ile.js" }, + { "img-src test1.example.com:*", + "img-src http://test1.example.com:*" }, + { "img-src test1.example.com:*/", + "img-src http://test1.example.com:*/" }, + { "img-src test1.example.com:*/path-1", + "img-src http://test1.example.com:*/path-1" }, + { "img-src test1.example.com:*/path-1/", + "img-src http://test1.example.com:*/path-1/" }, + { "img-src test1.example.com:*/path-1/path_2", + "img-src http://test1.example.com:*/path-1/path_2" }, + { "img-src test1.example.com:*/path-1/path_2/", + "img-src http://test1.example.com:*/path-1/path_2/" }, + { "img-src test1.example.com:*/path-1/path_2/file.js", + "img-src http://test1.example.com:*/path-1/path_2/file.js" }, + { "img-src test1.example.com:*/path-1/path_2/f.ile.js", + "img-src http://test1.example.com:*/path-1/path_2/f.ile.js" }, + { "img-src http://test1.example.com/abc//", + "img-src http://test1.example.com/abc//" }, + { "img-src https://test1.example.com/abc/def//", + "img-src https://test1.example.com/abc/def//" }, + { "img-src https://test1.example.com/abc/def/ghi//", + "img-src https://test1.example.com/abc/def/ghi//" }, + { "img-src http://test1.example.com:80/abc//", + "img-src http://test1.example.com:80/abc//" }, + { "img-src https://test1.example.com:80/abc/def//", + "img-src https://test1.example.com:80/abc/def//" }, + { "img-src https://test1.example.com:80/abc/def/ghi//", + "img-src https://test1.example.com:80/abc/def/ghi//" }, + { "img-src https://test1.example.com/abc////////////def/", + "img-src https://test1.example.com/abc////////////def/" }, + { "img-src https://test1.example.com/abc////////////", + "img-src https://test1.example.com/abc////////////" }, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// ============== TestBadGeneratedPoliciesForPathHandling ============ + +TEST(CSPParser, BadGeneratedPoliciesForPathHandling) +{ + static const PolicyTest policies[] = { + // clang-format off + { "img-src test1.example.com:88path-1/", + "img-src 'none'" }, + { "img-src test1.example.com:80.js", + "img-src 'none'" }, + { "img-src test1.example.com:*.js", + "img-src 'none'" }, + { "img-src test1.example.com:*.", + "img-src 'none'" }, + { "img-src http://test1.example.com//", + "img-src 'none'" }, + { "img-src http://test1.example.com:80//", + "img-src 'none'" }, + { "img-src http://test1.example.com:80abc", + "img-src 'none'" }, + // clang-format on + }; + + uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); + ASSERT_NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)); +} + +// ======================== TestFuzzyPolicies ======================== + +// Use a policy, eliminate one character at a time, +// and feed it as input to the parser. + +TEST(CSPParser, ShorteningPolicies) +{ + char pol[] = + "default-src http://www.sub1.sub2.example.com:88/path1/path2/ " + "'unsafe-inline' 'none'"; + uint32_t len = static_cast(sizeof(pol)); + + PolicyTest testPol[1]; + memset(&testPol[0].policy, '\0', kMaxPolicyLength * sizeof(char)); + + while (--len) { + memset(&testPol[0].policy, '\0', kMaxPolicyLength * sizeof(char)); + memcpy(&testPol[0].policy, &pol, len * sizeof(char)); + ASSERT_TRUE( + NS_SUCCEEDED(runTestSuite(testPol, 1, kFuzzyExpectedPolicyCount))); + } +} + +// ============================= TestFuzzyPolicies =================== + +// We generate kFuzzyRuns inputs by (pseudo) randomly picking from the 128 +// ASCII characters; feed them to the parser and verfy that the parser +// handles the input gracefully. +// +// Please note, that by using srand(0) we get deterministic results! + +#if RUN_OFFLINE_TESTS + +TEST(CSPParser, FuzzyPolicies) +{ + // init srand with 0 so we get same results + srand(0); + + PolicyTest testPol[1]; + memset(&testPol[0].policy, '\0', kMaxPolicyLength); + + for (uint32_t index = 0; index < kFuzzyRuns; index++) { + // randomly select the length of the next policy + uint32_t polLength = rand() % kMaxPolicyLength; + // reset memory of the policy string + memset(&testPol[0].policy, '\0', kMaxPolicyLength * sizeof(char)); + + for (uint32_t i = 0; i < polLength; i++) { + // fill the policy array with random ASCII chars + testPol[0].policy[i] = static_cast(rand() % 128); + } + ASSERT_TRUE( + NS_SUCCEEDED(runTestSuite(testPol, 1, kFuzzyExpectedPolicyCount))); + } +} + +#endif + +// ======================= TestFuzzyPoliciesIncDir =================== + +// In a similar fashion as in TestFuzzyPolicies, we again (pseudo) randomly +// generate input for the parser, but this time also include a valid directive +// followed by the random input. + +#if RUN_OFFLINE_TESTS + +TEST(CSPParser, FuzzyPoliciesIncDir) +{ + // init srand with 0 so we get same results + srand(0); + + PolicyTest testPol[1]; + memset(&testPol[0].policy, '\0', kMaxPolicyLength); + + char defaultSrc[] = "default-src "; + int defaultSrcLen = sizeof(defaultSrc) - 1; + // copy default-src into the policy array + memcpy(&testPol[0].policy, &defaultSrc, (defaultSrcLen * sizeof(char))); + + for (uint32_t index = 0; index < kFuzzyRuns; index++) { + // randomly select the length of the next policy + uint32_t polLength = rand() % (kMaxPolicyLength - defaultSrcLen); + // reset memory of the policy string, but leave default-src. + memset((&(testPol[0].policy) + (defaultSrcLen * sizeof(char))), '\0', + (kMaxPolicyLength - defaultSrcLen) * sizeof(char)); + + // do not start at index 0 so we do not overwrite 'default-src' + for (uint32_t i = defaultSrcLen; i < polLength; i++) { + // fill the policy array with random ASCII chars + testPol[0].policy[i] = static_cast(rand() % 128); + } + ASSERT_TRUE( + NS_SUCCEEDED(runTestSuite(testPol, 1, kFuzzyExpectedPolicyCount))); + } +} + +#endif + +// ====================== TestFuzzyPoliciesIncDirLimASCII ============ + +// Same as TestFuzzyPoliciesIncDir() but using limited ASCII, +// which represents more likely input. + +#if RUN_OFFLINE_TESTS + +TEST(CSPParser, FuzzyPoliciesIncDirLimASCII) +{ + char input[] = + "1234567890" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWZYZ" + "!@#^&*()-+_="; + + // init srand with 0 so we get same results + srand(0); + + PolicyTest testPol[1]; + memset(&testPol[0].policy, '\0', kMaxPolicyLength); + + char defaultSrc[] = "default-src "; + int defaultSrcLen = sizeof(defaultSrc) - 1; + // copy default-src into the policy array + memcpy(&testPol[0].policy, &defaultSrc, (defaultSrcLen * sizeof(char))); + + for (uint32_t index = 0; index < kFuzzyRuns; index++) { + // randomly select the length of the next policy + uint32_t polLength = rand() % (kMaxPolicyLength - defaultSrcLen); + // reset memory of the policy string, but leave default-src. + memset((&(testPol[0].policy) + (defaultSrcLen * sizeof(char))), '\0', + (kMaxPolicyLength - defaultSrcLen) * sizeof(char)); + + // do not start at index 0 so we do not overwrite 'default-src' + for (uint32_t i = defaultSrcLen; i < polLength; i++) { + // fill the policy array with chars from the pre-defined input + uint32_t inputIndex = rand() % sizeof(input); + testPol[0].policy[i] = input[inputIndex]; + } + ASSERT_TRUE( + NS_SUCCEEDED(runTestSuite(testPol, 1, kFuzzyExpectedPolicyCount))); + } +} +#endif diff --git a/dom/security/test/gtest/TestFilenameEvalParser.cpp b/dom/security/test/gtest/TestFilenameEvalParser.cpp new file mode 100644 index 0000000000..60683007ca --- /dev/null +++ b/dom/security/test/gtest/TestFilenameEvalParser.cpp @@ -0,0 +1,453 @@ +/* -*- 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/. */ + +#include "gtest/gtest.h" + +#include +#include + +#include "nsContentSecurityUtils.h" +#include "nsStringFwd.h" + +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SimpleGlobalObject.h" +#include "mozilla/extensions/WebExtensionPolicy.h" + +static constexpr auto kChromeURI = "chromeuri"_ns; +static constexpr auto kResourceURI = "resourceuri"_ns; +static constexpr auto kBlobUri = "bloburi"_ns; +static constexpr auto kDataUri = "dataurl"_ns; +static constexpr auto kAboutUri = "abouturi"_ns; +static constexpr auto kSingleString = "singlestring"_ns; +static constexpr auto kMozillaExtensionFile = "mozillaextension_file"_ns; +static constexpr auto kExtensionURI = "extension_uri"_ns; +static constexpr auto kSuspectedUserChromeJS = "suspectedUserChromeJS"_ns; +#if defined(XP_WIN) +static constexpr auto kSanitizedWindowsURL = "sanitizedWindowsURL"_ns; +static constexpr auto kSanitizedWindowsPath = "sanitizedWindowsPath"_ns; +#endif +static constexpr auto kOther = "other"_ns; + +#define ASSERT_AND_PRINT(first, second, condition) \ + fprintf(stderr, "First: %s\n", first.get()); \ + fprintf(stderr, "Second: %s\n", NS_ConvertUTF16toUTF8(second).get()); \ + ASSERT_TRUE((condition)); +// Usage: ASSERT_AND_PRINT(ret.first, ret.second.value(), ... + +#define ASSERT_AND_PRINT_FIRST(first, condition) \ + fprintf(stderr, "First: %s\n", (first).get()); \ + ASSERT_TRUE((condition)); +// Usage: ASSERT_AND_PRINT_FIRST(ret.first, ... + +TEST(FilenameEvalParser, ResourceChrome) +{ + { + constexpr auto str = u"chrome://firegestures/content/browser.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kChromeURI && ret.second.isSome() && + ret.second.value() == str); + } + { + constexpr auto str = u"resource://firegestures/content/browser.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kResourceURI && ret.second.isSome() && + ret.second.value() == str); + } +} + +TEST(FilenameEvalParser, BlobData) +{ + { + constexpr auto str = u"blob://000-000"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kBlobUri && !ret.second.isSome()); + } + { + constexpr auto str = u"blob:000-000"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kBlobUri && !ret.second.isSome()); + } + { + constexpr auto str = u"data://blahblahblah"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kDataUri && !ret.second.isSome()); + } + { + constexpr auto str = u"data:blahblahblah"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kDataUri && !ret.second.isSome()); + } +} + +TEST(FilenameEvalParser, MozExtension) +{ + { // Test shield.mozilla.org replacing + constexpr auto str = + u"jar:file:///c:/users/bob/appdata/roaming/mozilla/firefox/profiles/" + u"foo/" + "extensions/federated-learning@shield.mozilla.org.xpi!/experiments/" + "study/api.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kMozillaExtensionFile && + ret.second.value() == + u"federated-learning@s!/experiments/study/api.js"_ns); + } + { // Test mozilla.org replacing + constexpr auto str = + u"jar:file:///c:/users/bob/appdata/roaming/mozilla/firefox/profiles/" + u"foo/" + "extensions/federated-learning@shigeld.mozilla.org.xpi!/experiments/" + "study/api.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE( + ret.first == kMozillaExtensionFile && + ret.second.value() == + nsLiteralString( + u"federated-learning@shigeld.m!/experiments/study/api.js")); + } + { // Test truncating + constexpr auto str = + u"jar:file:///c:/users/bob/appdata/roaming/mozilla/firefox/profiles/" + u"foo/" + "extensions/federated-learning@shigeld.mozilla.org.xpi!/experiments/" + "study/apiiiiiiiiiiiiiiiiiiiiiiiiiiiiii.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kMozillaExtensionFile && + ret.second.value() == + u"federated-learning@shigeld.m!/experiments/" + "study/apiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"_ns); + } +} + +TEST(FilenameEvalParser, UserChromeJS) +{ + { + constexpr auto str = u"firegestures/content/browser.uc.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kSuspectedUserChromeJS && !ret.second.isSome()); + } + { + constexpr auto str = u"firegestures/content/browser.uc.js?"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kSuspectedUserChromeJS && !ret.second.isSome()); + } + { + constexpr auto str = u"firegestures/content/browser.uc.js?243244224"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kSuspectedUserChromeJS && !ret.second.isSome()); + } + { + constexpr auto str = + u"file:///b:/fxprofiles/mark/chrome/" + "addbookmarkherewithmiddleclick.uc.js?1558444389291"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kSuspectedUserChromeJS && !ret.second.isSome()); + } +} + +TEST(FilenameEvalParser, SingleFile) +{ + { + constexpr auto str = u"browser.uc.js?2456"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kSingleString && ret.second.isSome() && + ret.second.value() == str); + } + { + constexpr auto str = u"debugger"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kSingleString && ret.second.isSome() && + ret.second.value() == str); + } +} + +TEST(FilenameEvalParser, Other) +{ + { + constexpr auto str = u"firegestures--content"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); + } + { + constexpr auto str = u"gallop://thing/fire"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsURL && + ret.second.value() == u"gallop"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"gallop://fire"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsURL && + ret.second.value() == u"gallop"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"firegestures/content"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsPath && + ret.second.value() == u"content"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"firegestures\\content"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsPath && + ret.second.value() == u"content"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"/home/tom/files/thing"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsPath && + ret.second.value() == u"thing"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"file://c/uers/tom/file.txt"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsURL && + ret.second.value() == u"file://.../file.txt"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"c:/uers/tom/file.txt"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsPath && + ret.second.value() == u"file.txt"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"http://example.com/"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsURL && + ret.second.value() == u"http"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } + { + constexpr auto str = u"http://example.com/thing.html"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); +#if defined(XP_WIN) + ASSERT_TRUE(ret.first == kSanitizedWindowsURL && + ret.second.value() == u"http"_ns); +#else + ASSERT_TRUE(ret.first == kOther && !ret.second.isSome()); +#endif + } +} + +TEST(FilenameEvalParser, WebExtensionPathParser) +{ + { + // Set up an Extension and register it so we can test against it. + mozilla::dom::AutoJSAPI jsAPI; + ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsAPI.cx(); + + mozilla::dom::GlobalObject go(cx, xpc::PrivilegedJunkScope()); + auto* wEI = new mozilla::extensions::WebExtensionInit(); + + JS::Rooted func( + cx, (JSObject*)JS_NewFunction(cx, (JSNative)1, 0, 0, "customMethodA")); + JS::Rooted tempGlobalRoot(cx, JS::CurrentGlobalOrNull(cx)); + wEI->mLocalizeCallback = new mozilla::dom::WebExtensionLocalizeCallback( + cx, func, tempGlobalRoot, nullptr); + + wEI->mAllowedOrigins = + mozilla::dom::OwningMatchPatternSetOrStringSequence(); + nsString* slotPtr = + wEI->mAllowedOrigins.SetAsStringSequence().AppendElement( + mozilla::fallible); + ASSERT_TRUE(slotPtr != nullptr); + nsString& slot = *slotPtr; + slot.Truncate(); + slot = u"http://example.com"_ns; + + wEI->mName = u"gtest Test Extension"_ns; + wEI->mId = u"gtesttestextension@mozilla.org"_ns; + wEI->mBaseURL = u"file://foo"_ns; + wEI->mMozExtensionHostname = "e37c3c08-beac-a04b-8032-c4f699a1a856"_ns; + + mozilla::ErrorResult eR; + RefPtr w = + mozilla::extensions::WebExtensionPolicy::Constructor(go, *wEI, eR); + w->SetActive(true, eR); + + constexpr auto str = + u"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856/path/to/file.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, true); + + ASSERT_TRUE(ret.first == kExtensionURI && + ret.second.value() == + u"moz-extension://[gtesttestextension@mozilla.org: " + "gtest Test Extension]P=0/path/to/file.js"_ns); + + w->SetActive(false, eR); + + delete wEI; + } + { + // Set up an Extension and register it so we can test against it. + mozilla::dom::AutoJSAPI jsAPI; + ASSERT_TRUE(jsAPI.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsAPI.cx(); + + mozilla::dom::GlobalObject go(cx, xpc::PrivilegedJunkScope()); + auto wEI = new mozilla::extensions::WebExtensionInit(); + + JS::Rooted func( + cx, (JSObject*)JS_NewFunction(cx, (JSNative)1, 0, 0, "customMethodA")); + JS::Rooted tempGlobalRoot(cx, JS::CurrentGlobalOrNull(cx)); + wEI->mLocalizeCallback = new mozilla::dom::WebExtensionLocalizeCallback( + cx, func, tempGlobalRoot, NULL); + + wEI->mAllowedOrigins = + mozilla::dom::OwningMatchPatternSetOrStringSequence(); + nsString* slotPtr = + wEI->mAllowedOrigins.SetAsStringSequence().AppendElement( + mozilla::fallible); + nsString& slot = *slotPtr; + slot.Truncate(); + slot = u"http://example.com"_ns; + + wEI->mName = u"gtest Test Extension"_ns; + wEI->mId = u"gtesttestextension@mozilla.org"_ns; + wEI->mBaseURL = u"file://foo"_ns; + wEI->mMozExtensionHostname = "e37c3c08-beac-a04b-8032-c4f699a1a856"_ns; + wEI->mIsPrivileged = true; + + mozilla::ErrorResult eR; + RefPtr w = + mozilla::extensions::WebExtensionPolicy::Constructor(go, *wEI, eR); + w->SetActive(true, eR); + + constexpr auto str = + u"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856/path/to/file.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, true); + + ASSERT_TRUE(ret.first == kExtensionURI && + ret.second.value() == + u"moz-extension://[gtesttestextension@mozilla.org: " + "gtest Test Extension]P=1/path/to/file.js"_ns); + + w->SetActive(false, eR); + + delete wEI; + } + { + constexpr auto str = + u"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856/path/to/file.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kExtensionURI && !ret.second.isSome()); + } + { + constexpr auto str = + u"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856/file.js"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, true); + ASSERT_TRUE( + ret.first == kExtensionURI && + ret.second.value() == + nsLiteralString( + u"moz-extension://[failed finding addon by host]/file.js")); + } + { + constexpr auto str = + u"moz-extension://e37c3c08-beac-a04b-8032-c4f699a1a856/path/to/" + "file.js?querystringx=6"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, true); + ASSERT_TRUE(ret.first == kExtensionURI && + ret.second.value() == + u"moz-extension://[failed finding addon " + "by host]/path/to/file.js"_ns); + } +} + +TEST(FilenameEvalParser, AboutPageParser) +{ + { + constexpr auto str = u"about:about"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kAboutUri && + ret.second.value() == u"about:about"_ns); + } + { + constexpr auto str = u"about:about?hello"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kAboutUri && + ret.second.value() == u"about:about"_ns); + } + { + constexpr auto str = u"about:about#mom"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kAboutUri && + ret.second.value() == u"about:about"_ns); + } + { + constexpr auto str = u"about:about?hello=there#mom"_ns; + FilenameTypeAndDetails ret = + nsContentSecurityUtils::FilenameToFilenameType(str, false); + ASSERT_TRUE(ret.first == kAboutUri && + ret.second.value() == u"about:about"_ns); + } +} diff --git a/dom/security/test/gtest/TestSecureContext.cpp b/dom/security/test/gtest/TestSecureContext.cpp new file mode 100644 index 0000000000..dbfb4a63b6 --- /dev/null +++ b/dom/security/test/gtest/TestSecureContext.cpp @@ -0,0 +1,121 @@ +/* -*- 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/. */ + +#include "gtest/gtest.h" + +#include +#include + +#include "nsContentSecurityManager.h" +#include "nsContentUtils.h" +#include "nsIPrincipal.h" +#include "nsScriptSecurityManager.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/Preferences.h" + +using namespace mozilla; + +static const uint32_t kURIMaxLength = 64; + +struct TestExpectations { + char uri[kURIMaxLength]; + bool expectedResult; +}; + +class MOZ_RAII AutoRestoreBoolPref final { + public: + AutoRestoreBoolPref(const char* aPref, bool aValue) : mPref(aPref) { + Preferences::GetBool(mPref, &mOldValue); + Preferences::SetBool(mPref, aValue); + } + + ~AutoRestoreBoolPref() { Preferences::SetBool(mPref, mOldValue); } + + private: + const char* mPref = nullptr; + bool mOldValue = false; +}; + +// ============================= TestDirectives ======================== + +TEST(SecureContext, IsOriginPotentiallyTrustworthyWithContentPrincipal) +{ + // boolean isOriginPotentiallyTrustworthy(in nsIPrincipal aPrincipal); + + AutoRestoreBoolPref savedPref("network.proxy.allow_hijacking_localhost", + false); + + static const TestExpectations uris[] = { + {"http://example.com/", false}, + {"https://example.com/", true}, + {"ws://example.com/", false}, + {"wss://example.com/", true}, + {"file:///xyzzy", true}, + {"about:config", false}, + {"http://localhost", true}, + {"http://localhost.localhost", true}, + {"http://a.b.c.d.e.localhost", true}, + {"http://xyzzy.localhost", true}, + {"http://127.0.0.1", true}, + {"http://127.0.0.2", true}, + {"http://127.1.0.1", true}, + {"http://128.0.0.1", false}, + {"http://[::1]", true}, + {"http://[::ffff:127.0.0.1]", false}, + {"http://[::ffff:127.0.0.2]", false}, + {"http://[::ffff:7f00:1]", false}, + {"http://[::ffff:7f00:2]", false}, + {"resource://xyzzy", true}, + {"moz-extension://xyzzy", true}, + {"data:data:text/plain;charset=utf-8;base64,eHl6enk=", false}, + {"blob://unique-id", false}, + {"mailto:foo@bar.com", false}, + {"moz-icon://example.com", false}, + {"javascript:42", false}, + }; + + uint32_t numExpectations = sizeof(uris) / sizeof(TestExpectations); + nsCOMPtr csManager = + do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); + ASSERT_TRUE(!!csManager); + + nsresult rv; + for (uint32_t i = 0; i < numExpectations; i++) { + nsCOMPtr prin; + nsAutoCString uri(uris[i].uri); + rv = nsScriptSecurityManager::GetScriptSecurityManager() + ->CreateContentPrincipalFromOrigin(uri, getter_AddRefs(prin)); + ASSERT_EQ(rv, NS_OK); + bool isPotentiallyTrustworthy = prin->GetIsOriginPotentiallyTrustworthy(); + ASSERT_EQ(isPotentiallyTrustworthy, uris[i].expectedResult) + << uris[i].uri << uris[i].expectedResult; + } +} + +TEST(SecureContext, IsOriginPotentiallyTrustworthyWithSystemPrincipal) +{ + RefPtr ssManager = + nsScriptSecurityManager::GetScriptSecurityManager(); + ASSERT_TRUE(!!ssManager); + nsCOMPtr sysPrin = nsContentUtils::GetSystemPrincipal(); + bool isPotentiallyTrustworthy = sysPrin->GetIsOriginPotentiallyTrustworthy(); + ASSERT_TRUE(isPotentiallyTrustworthy); +} + +TEST(SecureContext, IsOriginPotentiallyTrustworthyWithNullPrincipal) +{ + RefPtr ssManager = + nsScriptSecurityManager::GetScriptSecurityManager(); + ASSERT_TRUE(!!ssManager); + + RefPtr nullPrin = + NullPrincipal::CreateWithoutOriginAttributes(); + bool isPotentiallyTrustworthy; + nsresult rv = + nullPrin->GetIsOriginPotentiallyTrustworthy(&isPotentiallyTrustworthy); + ASSERT_EQ(rv, NS_OK); + ASSERT_TRUE(!isPotentiallyTrustworthy); +} diff --git a/dom/security/test/gtest/TestSmartCrashTrimmer.cpp b/dom/security/test/gtest/TestSmartCrashTrimmer.cpp new file mode 100644 index 0000000000..d2238c0d75 --- /dev/null +++ b/dom/security/test/gtest/TestSmartCrashTrimmer.cpp @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#include "gtest/gtest.h" + +#include +#include +#include + +#include "nsContentSecurityUtils.h" +#include "nsTString.h" +#include "nsStringFwd.h" +#include "mozilla/Sprintf.h" + +#define ASSERT_STRCMP(first, second) ASSERT_TRUE(strcmp(first, second) == 0); + +#define ASSERT_STRCMP_AND_PRINT(first, second) \ + fprintf(stderr, "First: %s\n", first); \ + fprintf(stderr, "Second: %s\n", second); \ + fprintf(stderr, "strcmp = %i\n", strcmp(first, second)); \ + ASSERT_EQUAL(first, second); + +TEST(SmartCrashTrimmer, Test) +{ + static_assert(sPrintfCrashReasonSize == 1024); + { + auto ret = nsContentSecurityUtils::SmartFormatCrashString( + std::string(1025, '.').c_str()); + ASSERT_EQ(strlen(ret), 1023ul); + } + + { + auto ret = nsContentSecurityUtils::SmartFormatCrashString( + std::string(1025, '.').c_str(), std::string(1025, 'A').c_str(), + "Hello %s world %s!"); + char expected[1025]; + SprintfLiteral(expected, "Hello %s world AAAAAAAAAAAAAAAAAAAAAAAAA!", + std::string(984, '.').c_str()); + ASSERT_STRCMP(ret.get(), expected); + } +} diff --git a/dom/security/test/gtest/TestUnexpectedPrivilegedLoads.cpp b/dom/security/test/gtest/TestUnexpectedPrivilegedLoads.cpp new file mode 100644 index 0000000000..772e4bd353 --- /dev/null +++ b/dom/security/test/gtest/TestUnexpectedPrivilegedLoads.cpp @@ -0,0 +1,305 @@ +/* -*- 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/. */ + +#include "core/TelemetryEvent.h" +#include "gtest/gtest.h" +#include "js/Array.h" // JS::GetArrayLength +#include "js/PropertyAndElement.h" // JS_GetElement, JS_GetProperty +#include "js/TypeDecls.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +#include +#include +#include "nsContentSecurityManager.h" +#include "nsContentSecurityUtils.h" +#include "nsContentUtils.h" +#include "nsIContentPolicy.h" +#include "nsILoadInfo.h" +#include "nsNetUtil.h" +#include "nsStringFwd.h" + +using namespace mozilla; +using namespace TelemetryTestHelpers; + +extern Atomic sJSHacksChecked; +extern Atomic sJSHacksPresent; +extern Atomic sCSSHacksChecked; +extern Atomic sCSSHacksPresent; + +TEST_F(TelemetryTestFixture, UnexpectedPrivilegedLoadsTelemetryTest) { + // Disable JS/CSS Hacks Detection, which would consider this current profile + // as uninteresting for our measurements: + bool origJSHacksPresent = sJSHacksPresent; + bool origJSHacksChecked = sJSHacksChecked; + sJSHacksPresent = false; + sJSHacksChecked = true; + bool origCSSHacksPresent = sCSSHacksPresent; + bool origCSSHacksChecked = sCSSHacksChecked; + sCSSHacksPresent = false; + sCSSHacksChecked = true; + + struct testResults { + nsCString fileinfo; + nsCString extraValueContenttype; + nsCString extraValueRemotetype; + nsCString extraValueFiledetails; + nsCString extraValueRedirects; + }; + + struct testCasesAndResults { + nsCString urlstring; + nsContentPolicyType contentType; + nsCString remoteType; + testResults expected; + }; + + AutoJSContextWithGlobal cx(mCleanGlobal); + // Make sure we don't look at events from other tests. + Unused << mTelemetry->ClearEvents(); + + // required for telemetry lookups + constexpr auto category = "security"_ns; + constexpr auto method = "unexpectedload"_ns; + constexpr auto object = "systemprincipal"_ns; + constexpr auto extraKeyContenttype = "contenttype"_ns; + constexpr auto extraKeyRemotetype = "remotetype"_ns; + constexpr auto extraKeyFiledetails = "filedetails"_ns; + constexpr auto extraKeyRedirects = "redirects"_ns; + + // some cases from TestFilenameEvalParser + // no need to replicate all scenarios?! + testCasesAndResults myTestCases[] = { + {"chrome://firegestures/content/browser.js"_ns, + nsContentPolicyType::TYPE_SCRIPT, + "web"_ns, + {"chromeuri"_ns, "TYPE_SCRIPT"_ns, "web"_ns, + "chrome://firegestures/content/browser.js"_ns, ""_ns}}, + {"resource://firegestures/content/browser.js"_ns, + nsContentPolicyType::TYPE_SCRIPT, + "web"_ns, + {"resourceuri"_ns, "TYPE_SCRIPT"_ns, "web"_ns, + "resource://firegestures/content/browser.js"_ns, ""_ns}}, + {// test that we don't report blob details + // ..and test that we strip of URLs from remoteTypes + "blob://000-000"_ns, + nsContentPolicyType::TYPE_SCRIPT, + "webIsolated=https://blob.example/"_ns, + {"bloburi"_ns, "TYPE_SCRIPT"_ns, "webIsolated"_ns, "unknown"_ns, ""_ns}}, + {// test for cases where finalURI is null, due to a broken nested URI + // .. like malformed moz-icon URLs + "moz-icon:blahblah"_ns, + nsContentPolicyType::TYPE_DOCUMENT, + "web"_ns, + {"other"_ns, "TYPE_DOCUMENT"_ns, "web"_ns, "unknown"_ns, ""_ns}}, + {// we dont report data urls + // ..and test that we strip of URLs from remoteTypes + "data://blahblahblah"_ns, + nsContentPolicyType::TYPE_SCRIPT, + "webCOOP+COEP=https://data.example"_ns, + {"dataurl"_ns, "TYPE_SCRIPT"_ns, "webCOOP+COEP"_ns, "unknown"_ns, + ""_ns}}, + {// handle data URLs for webextension content scripts differently + // .. by noticing their annotation + "data:text/css;extension=style;charset=utf-8,/* some css here */"_ns, + nsContentPolicyType::TYPE_STYLESHEET, + "web"_ns, + {"dataurl-extension-contentstyle"_ns, "TYPE_STYLESHEET"_ns, "web"_ns, + "unknown"_ns, ""_ns}}, + {// we only report file URLs on windows, where we can easily sanitize + "file://c/users/tom/file.txt"_ns, + nsContentPolicyType::TYPE_SCRIPT, + "web"_ns, + { +#if defined(XP_WIN) + "sanitizedWindowsURL"_ns, "TYPE_SCRIPT"_ns, "web"_ns, + "file://.../file.txt"_ns, ""_ns + +#else + "other"_ns, "TYPE_SCRIPT"_ns, "web"_ns, "unknown"_ns, ""_ns +#endif + }}, + {// test for one redirect + "moz-extension://abcdefab-1234-4321-0000-abcdefabcdef/js/assets.js"_ns, + nsContentPolicyType::TYPE_SCRIPT, + "web"_ns, + {"extension_uri"_ns, "TYPE_SCRIPT"_ns, "web"_ns, + // the extension-id is made-up, so the extension will report failure + "moz-extension://[failed finding addon by host]/js/assets.js"_ns, + "https"_ns}}, + {// test for cases where finalURI is empty + ""_ns, + nsContentPolicyType::TYPE_STYLESHEET, + "web"_ns, + {"other"_ns, "TYPE_STYLESHEET"_ns, "web"_ns, "unknown"_ns, ""_ns}}, + {// test for cases where finalURI is null, due to the struct layout, we'll + // override the URL with nullptr in loop below. + "URLWillResultInNullPtr"_ns, + nsContentPolicyType::TYPE_SCRIPT, + "web"_ns, + {"other"_ns, "TYPE_SCRIPT"_ns, "web"_ns, "unknown"_ns, ""_ns}}, + }; + + int i = 0; + for (auto const& currentTest : myTestCases) { + nsresult rv; + nsCOMPtr uri; + + // special-casing for a case where the uri is null + if (!currentTest.urlstring.Equals("URLWillResultInNullPtr")) { + NS_NewURI(getter_AddRefs(uri), currentTest.urlstring); + } + + // We can't create channels for chrome: URLs unless they are in a chrome + // registry that maps them into the actual destination URL (usually + // file://). It seems that gtest don't have chrome manifest registered, so + // we'll use a mockChannel with a mockUri. + nsCOMPtr mockUri; + rv = NS_NewURI(getter_AddRefs(mockUri), "http://example.com"_ns); + ASSERT_EQ(rv, NS_OK) << "Could not create mockUri"; + nsCOMPtr mockChannel; + nsCOMPtr service = do_GetIOService(); + if (!service) { + ASSERT_TRUE(false) + << "Couldn't initialize IOService"; + } + rv = service->NewChannelFromURI( + mockUri, nullptr, nsContentUtils::GetSystemPrincipal(), + nsContentUtils::GetSystemPrincipal(), 0, currentTest.contentType, + getter_AddRefs(mockChannel)); + ASSERT_EQ(rv, NS_OK) << "Could not create a mock channel"; + nsCOMPtr mockLoadInfo = mockChannel->LoadInfo(); + + // We're adding a redirect entry for one specific test + if (currentTest.urlstring.EqualsASCII( + "moz-extension://abcdefab-1234-4321-0000-abcdefabcdef/js/" + "assets.js")) { + nsCOMPtr redirUri; + NS_NewURI(getter_AddRefs(redirUri), + "https://www.analytics.example/analytics.js"_ns); + nsCOMPtr redirPrincipal = + BasePrincipal::CreateContentPrincipal(redirUri, OriginAttributes()); + nsCOMPtr redirectChannel; + Unused << service->NewChannelFromURI(redirUri, nullptr, redirPrincipal, + nullptr, 0, currentTest.contentType, + getter_AddRefs(redirectChannel)); + + mockLoadInfo->AppendRedirectHistoryEntry(redirectChannel, false); + } + + // this will record the event + nsContentSecurityManager::MeasureUnexpectedPrivilegedLoads( + mockLoadInfo, uri, currentTest.remoteType); + + // let's inspect the recorded events + + JS::Rooted eventsSnapshot(cx.GetJSContext()); + GetEventSnapshot(cx.GetJSContext(), &eventsSnapshot); + + ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, category, + method, object)) + << "Test event with value and extra must be present."; + + // Convert eventsSnapshot into array/object + JSContext* aCx = cx.GetJSContext(); + JS::Rooted arrayObj(aCx, &eventsSnapshot.toObject()); + + JS::Rooted eventRecord(aCx); + ASSERT_TRUE(JS_GetElement(aCx, arrayObj, i++, &eventRecord)) + << "Must be able to get record."; // record is already undefined :-/ + + ASSERT_TRUE(!eventRecord.isUndefined()) + << "eventRecord should not be undefined"; + + JS::Rooted recordArray(aCx, &eventRecord.toObject()); + uint32_t recordLength; + ASSERT_TRUE(JS::GetArrayLength(aCx, recordArray, &recordLength)) + << "Event record array must have length."; + ASSERT_TRUE(recordLength == 6) + << "Event record must have 6 elements."; + + JS::Rooted str(aCx); + nsAutoJSString jsStr; + // The fileinfo string is at index 4 + ASSERT_TRUE(JS_GetElement(aCx, recordArray, 4, &str)) + << "Must be able to get value."; + ASSERT_TRUE(jsStr.init(aCx, str)) + << "Value must be able to be init'd to a jsstring."; + + ASSERT_STREQ(NS_ConvertUTF16toUTF8(jsStr).get(), + currentTest.expected.fileinfo.get()) + << "Reported fileinfo '" << NS_ConvertUTF16toUTF8(jsStr).get() + << " 'equals expected value: " << currentTest.expected.fileinfo.get(); + + // Extra is at index 5 + JS::Rooted obj(aCx); + ASSERT_TRUE(JS_GetElement(aCx, recordArray, 5, &obj)) + << "Must be able to get extra data"; + JS::Rooted extraObj(aCx, &obj.toObject()); + // looking at remotetype extra for content type + JS::Rooted extraValC(aCx); + ASSERT_TRUE( + JS_GetProperty(aCx, extraObj, extraKeyContenttype.get(), &extraValC)) + << "Must be able to get the extra key's value for contenttype"; + ASSERT_TRUE(jsStr.init(aCx, extraValC)) + << "Extra value contenttype must be able to be init'd to a jsstring."; + ASSERT_STREQ(NS_ConvertUTF16toUTF8(jsStr).get(), + currentTest.expected.extraValueContenttype.get()) + << "Reported value for extra contenttype '" + << NS_ConvertUTF16toUTF8(jsStr).get() + << "' should equals supplied value" + << currentTest.expected.extraValueContenttype.get(); + // and again for remote type + JS::Rooted extraValP(aCx); + ASSERT_TRUE( + JS_GetProperty(aCx, extraObj, extraKeyRemotetype.get(), &extraValP)) + << "Must be able to get the extra key's value for remotetype"; + ASSERT_TRUE(jsStr.init(aCx, extraValP)) + << "Extra value remotetype must be able to be init'd to a jsstring."; + ASSERT_STREQ(NS_ConvertUTF16toUTF8(jsStr).get(), + currentTest.expected.extraValueRemotetype.get()) + << "Reported value for extra remotetype '" + << NS_ConvertUTF16toUTF8(jsStr).get() + << "' should equals supplied value: " + << currentTest.expected.extraValueRemotetype.get(); + // repeating the same for filedetails extra + JS::Rooted extraValF(aCx); + ASSERT_TRUE( + JS_GetProperty(aCx, extraObj, extraKeyFiledetails.get(), &extraValF)) + << "Must be able to get the extra key's value for filedetails"; + ASSERT_TRUE(jsStr.init(aCx, extraValF)) + << "Extra value filedetails must be able to be init'd to a jsstring."; + ASSERT_STREQ(NS_ConvertUTF16toUTF8(jsStr).get(), + currentTest.expected.extraValueFiledetails.get()) + << "Reported value for extra filedetails '" + << NS_ConvertUTF16toUTF8(jsStr).get() << "'should equals supplied value" + << currentTest.expected.extraValueFiledetails.get(); + // checking the extraKeyRedirects match + JS::Rooted extraValRedirects(aCx); + ASSERT_TRUE(JS_GetProperty(aCx, extraObj, extraKeyRedirects.get(), + &extraValRedirects)) + << "Must be able to get the extra value for redirects"; + ASSERT_TRUE(jsStr.init(aCx, extraValRedirects)) + << "Extra value redirects must be able to be init'd to a jsstring"; + ASSERT_STREQ(NS_ConvertUTF16toUTF8(jsStr).get(), + currentTest.expected.extraValueRedirects.get()) + << "Reported value for extra redirect '" + << NS_ConvertUTF16toUTF8(jsStr).get() + << "' should equals supplied value: " + << currentTest.expected.extraValueRedirects.get(); + } + + // Re-store JS/CSS hacks detection state + sJSHacksPresent = origJSHacksPresent; + sJSHacksChecked = origJSHacksChecked; + sCSSHacksPresent = origCSSHacksPresent; + sCSSHacksChecked = origCSSHacksChecked; +} diff --git a/dom/security/test/gtest/moz.build b/dom/security/test/gtest/moz.build new file mode 100644 index 0000000000..c9ab4dcece --- /dev/null +++ b/dom/security/test/gtest/moz.build @@ -0,0 +1,25 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "TestCSPParser.cpp", + "TestFilenameEvalParser.cpp", + "TestSecureContext.cpp", + "TestSmartCrashTrimmer.cpp", +] + +if CONFIG["OS_TARGET"] != "Android": + UNIFIED_SOURCES += [ + "TestUnexpectedPrivilegedLoads.cpp", + ] + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/caps", + "/toolkit/components/telemetry/", + "/toolkit/components/telemetry/tests/gtest", +] -- cgit v1.2.3