commit dae353ff5f2d033b9b87ebf2a5f833c87068d4a3
Author: Valentin Gosu <valentin.gosu(a)gmail.com>
Date: Thu Jun 21 00:09:15 2018 +0200
Bug 1412081 - Add ability to blacklist file paths on Unix platforms. r=mayhemer, a=RyanVM
--HG--
extra : source : 92ff98e2731eac0558cbc7e9c71e521246772240
extra : amend_source : e01976f9592cd2635c075cc6031e81a1b1e1b8bd
---
dom/ipc/ContentPrefs.cpp | 1 +
xpcom/io/FilePreferences.cpp | 329 ++++++++++++++++----------
xpcom/io/FilePreferences.h | 6 +
xpcom/io/nsLocalFileUnix.cpp | 58 +++++
xpcom/tests/gtest/TestFilePreferencesUnix.cpp | 203 ++++++++++++++++
xpcom/tests/gtest/TestFilePreferencesWin.cpp | 4 +
xpcom/tests/gtest/moz.build | 5 +
7 files changed, 485 insertions(+), 121 deletions(-)
diff --git a/dom/ipc/ContentPrefs.cpp b/dom/ipc/ContentPrefs.cpp
index d011c7393125..ac1ea109fc9f 100644
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -270,6 +270,7 @@ const char* mozilla::dom::ContentPrefs::gEarlyPrefs[] = {
"network.dns.disablePrefetch",
"network.dns.disablePrefetchFromHTTPS",
"network.file.disable_unc_paths",
+ "network.file.path_blacklist",
"network.http.tailing.enabled",
"network.jar.block-remote-files",
"network.loadinfo.skip_type_assertion",
diff --git a/xpcom/io/FilePreferences.cpp b/xpcom/io/FilePreferences.cpp
index 3ad0e0ee19e0..9467c055d9bf 100644
--- a/xpcom/io/FilePreferences.cpp
+++ b/xpcom/io/FilePreferences.cpp
@@ -6,7 +6,11 @@
#include "FilePreferences.h"
+#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Unused.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
@@ -15,15 +19,37 @@ namespace mozilla {
namespace FilePreferences {
static bool sBlockUNCPaths = false;
-typedef nsTArray<nsString> Paths;
+typedef nsTArray<nsString> WinPaths;
+static StaticAutoPtr<WinPaths> sWhitelist;
-static Paths& PathArray()
+static WinPaths& PathWhitelist()
{
- static Paths sPaths;
- return sPaths;
+ if (!sWhitelist) {
+ sWhitelist = new nsTArray<nsString>();
+ ClearOnShutdown(&sWhitelist);
+ }
+ return *sWhitelist;
+}
+
+#ifdef XP_WIN
+typedef char16_t char_path_t;
+#else
+typedef char char_path_t;
+#endif
+
+typedef nsTArray<nsTString<char_path_t>> Paths;
+static StaticAutoPtr<Paths> sBlacklist;
+
+static Paths& PathBlacklist()
+{
+ if (!sBlacklist) {
+ sBlacklist = new nsTArray<nsTString<char_path_t>>();
+ ClearOnShutdown(&sBlacklist);
+ }
+ return *sBlacklist;
}
-static void AllowDirectory(char const* directory)
+static void AllowUNCDirectory(char const* directory)
{
nsCOMPtr<nsIFile> file;
NS_GetSpecialDirectory(directory, getter_AddRefs(file));
@@ -43,180 +69,202 @@ static void AllowDirectory(char const* directory)
return;
}
- if (!PathArray().Contains(path)) {
- PathArray().AppendElement(path);
+ if (!PathWhitelist().Contains(path)) {
+ PathWhitelist().AppendElement(path);
}
}
void InitPrefs()
{
sBlockUNCPaths = Preferences::GetBool("network.file.disable_unc_paths", false);
+
+ PathBlacklist().Clear();
+ nsAutoCString blacklist;
+ Preferences::GetCString("network.file.path_blacklist", blacklist);
+
+ Tokenizer p(blacklist);
+ while (!p.CheckEOF()) {
+ nsCString path;
+ Unused << p.ReadUntil(Tokenizer::Token::Char(','), path);
+ path.Trim(" ");
+ if (!path.IsEmpty()) {
+#ifdef XP_WIN
+ PathBlacklist().AppendElement(NS_ConvertASCIItoUTF16(path));
+#else
+ PathBlacklist().AppendElement(path);
+#endif
+ }
+ Unused << p.CheckChar(',');
+ }
}
void InitDirectoriesWhitelist()
{
// NS_GRE_DIR is the installation path where the binary resides.
- AllowDirectory(NS_GRE_DIR);
+ AllowUNCDirectory(NS_GRE_DIR);
// NS_APP_USER_PROFILE_50_DIR and NS_APP_USER_PROFILE_LOCAL_50_DIR are the two
// parts of the profile we store permanent and local-specific data.
- AllowDirectory(NS_APP_USER_PROFILE_50_DIR);
- AllowDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR);
+ AllowUNCDirectory(NS_APP_USER_PROFILE_50_DIR);
+ AllowUNCDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR);
}
namespace { // anon
-class Normalizer
+template <typename TChar>
+class TNormalizer
{
public:
- Normalizer(const nsAString& aFilePath, const char16_t aSeparator);
- bool Get(nsAString& aNormalizedFilePath);
-
-private:
- bool ConsumeItem();
- bool ConsumeSeparator();
- bool IsEOF() { return mFilePathCursor == mFilePathEnd; }
+ TNormalizer(const nsTSubstring<TChar>& aFilePath, const TChar aSeparator)
+ : mFilePathCursor(aFilePath.BeginReading())
+ , mFilePathEnd(aFilePath.EndReading())
+ , mSeparator(aSeparator)
+ {
+ }
- bool ConsumeName();
- bool CheckParentDir();
- bool CheckCurrentDir();
+ bool Get(nsTSubstring<TChar>& aNormalizedFilePath)
+ {
+ aNormalizedFilePath.Truncate();
+
+ // Windows UNC paths begin with double separator (\\)
+ // Linux paths begin with just one separator (/)
+ // If we want to use the normalizer for regular windows paths this code
+ // will need to be updated.
+#ifdef XP_WIN
+ if (IsEOF()) {
+ return true;
+ }
+ if (ConsumeSeparator()) {
+ aNormalizedFilePath.Append(mSeparator);
+ }
+#endif
- nsString::const_char_iterator mFilePathCursor;
- nsString::const_char_iterator mFilePathEnd;
+ if (IsEOF()) {
+ return true;
+ }
+ if (ConsumeSeparator()) {
+ aNormalizedFilePath.Append(mSeparator);
+ }
- nsDependentSubstring mItem;
- char16_t const mSeparator;
- nsTArray<nsDependentSubstring> mStack;
-};
+ while (!IsEOF()) {
+ if (!ConsumeName()) {
+ return false;
+ }
+ }
-Normalizer::Normalizer(const nsAString& aFilePath, const char16_t aSeparator)
- : mFilePathCursor(aFilePath.BeginReading())
- , mFilePathEnd(aFilePath.EndReading())
- , mSeparator(aSeparator)
-{
-}
+ for (auto const& name : mStack) {
+ aNormalizedFilePath.Append(name);
+ }
-bool Normalizer::ConsumeItem()
-{
- if (IsEOF()) {
- return false;
+ return true;
}
- nsString::const_char_iterator nameBegin = mFilePathCursor;
- while (mFilePathCursor != mFilePathEnd) {
- if (*mFilePathCursor == mSeparator) {
- break; // don't include the separator
+private:
+ bool ConsumeItem()
+ {
+ if (IsEOF()) {
+ return false;
}
- ++mFilePathCursor;
- }
-
- mItem.Rebind(nameBegin, mFilePathCursor);
- return true;
-}
-bool Normalizer::ConsumeSeparator()
-{
- if (IsEOF()) {
- return false;
- }
+ typename nsTString<TChar>::const_char_iterator nameBegin = mFilePathCursor;
+ while (mFilePathCursor != mFilePathEnd) {
+ if (*mFilePathCursor == mSeparator) {
+ break; // don't include the separator
+ }
+ ++mFilePathCursor;
+ }
- if (*mFilePathCursor != mSeparator) {
- return false;
+ mItem.Rebind(nameBegin, mFilePathCursor);
+ return true;
}
- ++mFilePathCursor;
- return true;
-}
+ bool ConsumeSeparator()
+ {
+ if (IsEOF()) {
+ return false;
+ }
-bool Normalizer::Get(nsAString& aNormalizedFilePath)
-{
- aNormalizedFilePath.Truncate();
+ if (*mFilePathCursor != mSeparator) {
+ return false;
+ }
- if (IsEOF()) {
+ ++mFilePathCursor;
return true;
}
- if (ConsumeSeparator()) {
- aNormalizedFilePath.Append(mSeparator);
- }
- if (IsEOF()) {
- return true;
- }
- if (ConsumeSeparator()) {
- aNormalizedFilePath.Append(mSeparator);
- }
+ bool IsEOF() { return mFilePathCursor == mFilePathEnd; }
- while (!IsEOF()) {
- if (!ConsumeName()) {
- return false;
+ bool ConsumeName()
+ {
+ if (!ConsumeItem()) {
+ return true;
}
- }
-
- for (auto const& name : mStack) {
- aNormalizedFilePath.Append(name);
- }
- return true;
-}
+ if (CheckCurrentDir()) {
+ return true;
+ }
-bool Normalizer::ConsumeName()
-{
- if (!ConsumeItem()) {
- return true;
- }
+ if (CheckParentDir()) {
+ if (!mStack.Length()) {
+ // This means there are more \.. than valid names
+ return false;
+ }
- if (CheckCurrentDir()) {
- return true;
- }
+ mStack.RemoveElementAt(mStack.Length() - 1);
+ return true;
+ }
- if (CheckParentDir()) {
- if (!mStack.Length()) {
- // This means there are more \.. than valid names
+ if (mItem.IsEmpty()) {
+ // this means an empty name (a lone slash), which is illegal
return false;
}
- mStack.RemoveElementAt(mStack.Length() - 1);
+ if (ConsumeSeparator()) {
+ mItem.Rebind(mItem.BeginReading(), mFilePathCursor);
+ }
+ mStack.AppendElement(mItem);
+
return true;
}
- if (mItem.IsEmpty()) {
- // this means an empty name (a lone slash), which is illegal
- return false;
- }
+ bool CheckParentDir()
+ {
+ if (mItem.EqualsLiteral("..")) {
+ ConsumeSeparator();
+ // EOF is acceptable
+ return true;
+ }
- if (ConsumeSeparator()) {
- mItem.Rebind(mItem.BeginReading(), mFilePathCursor);
+ return false;
}
- mStack.AppendElement(mItem);
- return true;
-}
+ bool CheckCurrentDir()
+ {
+ if (mItem.EqualsLiteral(".")) {
+ ConsumeSeparator();
+ // EOF is acceptable
+ return true;
+ }
-bool Normalizer::CheckCurrentDir()
-{
- if (mItem == NS_LITERAL_STRING(".")) {
- ConsumeSeparator();
- // EOF is acceptable
- return true;
+ return false;
}
- return false;
-}
-
-bool Normalizer::CheckParentDir()
-{
- if (mItem == NS_LITERAL_STRING("..")) {
- ConsumeSeparator();
- // EOF is acceptable
- return true;
- }
+ typename nsTString<TChar>::const_char_iterator mFilePathCursor;
+ typename nsTString<TChar>::const_char_iterator mFilePathEnd;
- return false;
-}
+ nsTDependentSubstring<TChar> mItem;
+ TChar const mSeparator;
+ nsTArray<nsTDependentSubstring<TChar>> mStack;
+};
} // anon
bool IsBlockedUNCPath(const nsAString& aFilePath)
{
+ typedef TNormalizer<char16_t> Normalizer;
+ if (!sWhitelist) {
+ return false;
+ }
+
if (!sBlockUNCPaths) {
return false;
}
@@ -231,7 +279,7 @@ bool IsBlockedUNCPath(const nsAString& aFilePath)
return true;
}
- for (const auto& allowedPrefix : PathArray()) {
+ for (const auto& allowedPrefix : PathWhitelist()) {
if (StringBeginsWith(normalized, allowedPrefix)) {
if (normalized.Length() == allowedPrefix.Length()) {
return false;
@@ -251,6 +299,44 @@ bool IsBlockedUNCPath(const nsAString& aFilePath)
return true;
}
+#ifdef XP_WIN
+const char16_t kPathSeparator = L'\\';
+#else
+const char kPathSeparator = '/';
+#endif
+
+bool IsAllowedPath(const nsTSubstring<char_path_t>& aFilePath)
+{
+ typedef TNormalizer<char_path_t> Normalizer;
+ // If sBlacklist has been cleared at shutdown, we must avoid calling
+ // PathBlacklist() again, as that will recreate the array and we will leak.
+ if (!sBlacklist) {
+ return true;
+ }
+
+ if (PathBlacklist().Length() == 0) {
+ return true;
+ }
+
+ nsTAutoString<char_path_t> normalized;
+ if (!Normalizer(aFilePath, kPathSeparator).Get(normalized)) {
+ // Broken paths are considered invalid and thus inaccessible
+ return false;
+ }
+
+ for (const auto& prefix : PathBlacklist()) {
+ if (StringBeginsWith(normalized, prefix)) {
+ if (normalized.Length() > prefix.Length() &&
+ normalized[prefix.Length()] != kPathSeparator) {
+ continue;
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
void testing::SetBlockUNCPaths(bool aBlock)
{
sBlockUNCPaths = aBlock;
@@ -258,11 +344,12 @@ void testing::SetBlockUNCPaths(bool aBlock)
void testing::AddDirectoryToWhitelist(nsAString const & aPath)
{
- PathArray().AppendElement(aPath);
+ PathWhitelist().AppendElement(aPath);
}
bool testing::NormalizePath(nsAString const & aPath, nsAString & aNormalized)
{
+ typedef TNormalizer<char16_t> Normalizer;
Normalizer normalizer(aPath, L'\\');
return normalizer.Get(aNormalized);
}
diff --git a/xpcom/io/FilePreferences.h b/xpcom/io/FilePreferences.h
index fa281f9e6799..71c244201735 100644
--- a/xpcom/io/FilePreferences.h
+++ b/xpcom/io/FilePreferences.h
@@ -13,6 +13,12 @@ void InitPrefs();
void InitDirectoriesWhitelist();
bool IsBlockedUNCPath(const nsAString& aFilePath);
+#ifdef XP_WIN
+bool IsAllowedPath(const nsAString& aFilePath);
+#else
+bool IsAllowedPath(const nsACString& aFilePath);
+#endif
+
namespace testing {
void SetBlockUNCPaths(bool aBlock);
diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp
index 768f66b301ec..cc241b179ab4 100644
--- a/xpcom/io/nsLocalFileUnix.cpp
+++ b/xpcom/io/nsLocalFileUnix.cpp
@@ -12,6 +12,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Sprintf.h"
+#include "mozilla/FilePreferences.h"
#include <sys/types.h>
#include <sys/stat.h>
@@ -84,6 +85,8 @@ using namespace mozilla;
do { \
if (mPath.IsEmpty()) \
return NS_ERROR_NOT_INITIALIZED; \
+ if (!FilePreferences::IsAllowedPath(mPath)) \
+ return NS_ERROR_FILE_ACCESS_DENIED; \
} while(0)
/* directory enumerator */
@@ -140,6 +143,13 @@ nsDirEnumeratorUnix::Init(nsLocalFile* aParent,
return NS_ERROR_FILE_INVALID_PATH;
}
+ // When enumerating the directory, the paths must have a slash at the end.
+ nsAutoCString dirPathWithSlash(dirPath);
+ dirPathWithSlash.Append('/');
+ if (!FilePreferences::IsAllowedPath(dirPathWithSlash)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
if (NS_FAILED(aParent->GetNativePath(mParentPath))) {
return NS_ERROR_FAILURE;
}
@@ -269,6 +279,11 @@ nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter,
bool
nsLocalFile::FillStatCache()
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ errno = EACCES;
+ return false;
+ }
+
if (STAT(mPath.get(), &mCachedStat) == -1) {
// try lstat it may be a symlink
if (LSTAT(mPath.get(), &mCachedStat) == -1) {
@@ -311,6 +326,11 @@ nsLocalFile::InitWithNativePath(const nsACString& aFilePath)
mPath = aFilePath;
}
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ mPath.Truncate();
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
// trim off trailing slashes
ssize_t len = mPath.Length();
while ((len > 1) && (mPath[len - 1] == '/')) {
@@ -324,6 +344,10 @@ nsLocalFile::InitWithNativePath(const nsACString& aFilePath)
NS_IMETHODIMP
nsLocalFile::CreateAllAncestors(uint32_t aPermissions)
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
// <jband> I promise to play nice
char* buffer = mPath.BeginWriting();
char* slashp = buffer;
@@ -395,6 +419,9 @@ NS_IMETHODIMP
nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
PRFileDesc** aResult)
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
*aResult = PR_Open(mPath.get(), aFlags, aMode);
if (!*aResult) {
return NS_ErrorAccordingToNSPR();
@@ -416,6 +443,9 @@ nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
NS_IMETHODIMP
nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult)
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
*aResult = fopen(mPath.get(), aMode);
if (!*aResult) {
return NS_ERROR_FAILURE;
@@ -442,6 +472,10 @@ nsresult
nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags,
uint32_t aPermissions, PRFileDesc** aResult)
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
return NS_ERROR_FILE_UNKNOWN_TYPE;
}
@@ -491,6 +525,10 @@ nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags,
NS_IMETHODIMP
nsLocalFile::Create(uint32_t aType, uint32_t aPermissions)
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
PRFileDesc* junk = nullptr;
nsresult rv = CreateAndKeepOpen(aType,
PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE |
@@ -546,6 +584,10 @@ nsLocalFile::Normalize()
char resolved_path[PATH_MAX] = "";
char* resolved_path_ptr = nullptr;
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
resolved_path_ptr = realpath(mPath.get(), resolved_path);
// if there is an error, the return is null.
@@ -1017,6 +1059,10 @@ nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName)
return rv;
}
+ if (!FilePreferences::IsAllowedPath(newPathName)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
// try for atomic rename, falling back to copy/delete
if (rename(mPath.get(), newPathName.get()) < 0) {
if (errno == EXDEV) {
@@ -1959,6 +2005,10 @@ nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor)
NS_IMETHODIMP
nsLocalFile::Reveal()
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
#ifdef MOZ_WIDGET_GTK
nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
if (!giovfs) {
@@ -2002,6 +2052,10 @@ nsLocalFile::Reveal()
NS_IMETHODIMP
nsLocalFile::Launch()
{
+ if (!FilePreferences::IsAllowedPath(mPath)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
#ifdef MOZ_WIDGET_GTK
nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
if (!giovfs) {
@@ -2156,6 +2210,10 @@ nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName)
return rv;
}
+ if (!FilePreferences::IsAllowedPath(newPathName)) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
// try for atomic rename
if (rename(mPath.get(), newPathName.get()) < 0) {
if (errno == EXDEV) {
diff --git a/xpcom/tests/gtest/TestFilePreferencesUnix.cpp b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp
new file mode 100644
index 000000000000..c19928fcaec4
--- /dev/null
+++ b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp
@@ -0,0 +1,203 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/FilePreferences.h"
+
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ScopeExit.h"
+#include "nsISimpleEnumerator.h"
+
+using namespace mozilla;
+
+TEST(TestFilePreferencesUnix, Parsing)
+{
+ #define kBlacklisted "/tmp/blacklisted"
+ #define kBlacklistedDir "/tmp/blacklisted/"
+ #define kBlacklistedFile "/tmp/blacklisted/file"
+ #define kOther "/tmp/other"
+ #define kOtherDir "/tmp/other/"
+ #define kOtherFile "/tmp/other/file"
+ #define kAllowed "/tmp/allowed"
+
+ // This is run on exit of this function to make sure we clear the pref
+ // and that behaviour with the pref cleared is correct.
+ auto cleanup = MakeScopeExit([&] {
+ nsresult rv = Preferences::ClearUser("network.file.path_blacklist");
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kBlacklisted)), true);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kBlacklistedDir)), true);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kBlacklistedFile)), true);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kAllowed)), true);
+ });
+
+ auto CheckPrefs = [](const nsACString& aPaths)
+ {
+ nsresult rv;
+ rv = Preferences::SetCString("network.file.path_blacklist", aPaths);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kBlacklistedDir)), false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kBlacklistedDir)), false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kBlacklistedFile)), false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kBlacklisted)), false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kAllowed)), true);
+ };
+
+ CheckPrefs(NS_LITERAL_CSTRING(kBlacklisted));
+ CheckPrefs(NS_LITERAL_CSTRING(kBlacklisted "," kOther));
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kOtherFile)), false);
+ CheckPrefs(NS_LITERAL_CSTRING(kBlacklisted "," kOther ","));
+ ASSERT_EQ(FilePreferences::IsAllowedPath(NS_LITERAL_CSTRING(kOtherFile)), false);
+}
+
+TEST(TestFilePreferencesUnix, Simple)
+{
+ nsAutoCString tempPath;
+
+ // This is the directory we will blacklist
+ nsCOMPtr<nsIFile> blacklistedDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(blacklistedDir));
+ ASSERT_EQ(rv, NS_OK);
+ rv = blacklistedDir->GetNativePath(tempPath);
+ ASSERT_EQ(rv, NS_OK);
+ rv = blacklistedDir->AppendNative(NS_LITERAL_CSTRING("blacklisted_dir"));
+ ASSERT_EQ(rv, NS_OK);
+
+ // This is executed at exit to clean up after ourselves.
+ auto cleanup = MakeScopeExit([&] {
+ nsresult rv = Preferences::ClearUser("network.file.path_blacklist");
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+
+ rv = blacklistedDir->Remove(true);
+ ASSERT_EQ(rv, NS_OK);
+ });
+
+ // Create the directory
+ rv = blacklistedDir->Create(nsIFile::DIRECTORY_TYPE, 0666);
+ ASSERT_EQ(rv, NS_OK);
+
+ // This is the file we will try to access
+ nsCOMPtr<nsIFile> blacklistedFile;
+ rv = blacklistedDir->Clone(getter_AddRefs(blacklistedFile));
+ ASSERT_EQ(rv, NS_OK);
+ rv = blacklistedFile->AppendNative(NS_LITERAL_CSTRING("test_file"));
+
+ // Create the file
+ ASSERT_EQ(rv, NS_OK);
+ rv = blacklistedFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+
+ // Get the path for the blacklist
+ nsAutoCString blackListPath;
+ rv = blacklistedDir->GetNativePath(blackListPath);
+ ASSERT_EQ(rv, NS_OK);
+
+ // Set the pref and make sure it is enforced
+ rv = Preferences::SetCString("network.file.path_blacklist", blackListPath);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+
+ // Check that we can't access some of the file attributes
+ int64_t size;
+ rv = blacklistedFile->GetFileSize(&size);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ bool exists;
+ rv = blacklistedFile->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that we can't enumerate the directory
+ nsCOMPtr<nsISimpleEnumerator> dirEnumerator;
+ rv = blacklistedDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ nsCOMPtr<nsIFile> newPath;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative(NS_LITERAL_CSTRING("."));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative(NS_LITERAL_CSTRING("blacklisted_dir"));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ rv = newPath->AppendNative(NS_LITERAL_CSTRING("test_file"));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that ./ does not bypass the filter
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendRelativeNativePath(NS_LITERAL_CSTRING("./blacklisted_dir/file"));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that .. does not bypass the filter
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendRelativeNativePath(NS_LITERAL_CSTRING("allowed/../blacklisted_dir/file"));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative(NS_LITERAL_CSTRING("allowed"));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative(NS_LITERAL_CSTRING(".."));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative(NS_LITERAL_CSTRING("blacklisted_dir"));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ nsAutoCString trickyPath(tempPath);
+ trickyPath.AppendLiteral("/allowed/../blacklisted_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that we can't construct a path that is functionally the same
+ // as the blacklisted one and bypasses the filter.
+ trickyPath = tempPath;
+ trickyPath.AppendLiteral("/./blacklisted_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ trickyPath = tempPath;
+ trickyPath.AppendLiteral("//blacklisted_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ trickyPath.Truncate();
+ trickyPath.AppendLiteral("//");
+ trickyPath.Append(tempPath);
+ trickyPath.AppendLiteral("/blacklisted_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ trickyPath.Truncate();
+ trickyPath.AppendLiteral("//");
+ trickyPath.Append(tempPath);
+ trickyPath.AppendLiteral("//blacklisted_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that if the blacklisted string is a directory, we only block access
+ // to subresources, not the directory itself.
+ nsAutoCString blacklistDirPath(blackListPath);
+ blacklistDirPath.Append("/");
+ rv = Preferences::SetCString("network.file.path_blacklist", blacklistDirPath);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+
+ // This should work, since we only block subresources
+ rv = blacklistedDir->Exists(&exists);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = blacklistedDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+}
diff --git a/xpcom/tests/gtest/TestFilePreferencesWin.cpp b/xpcom/tests/gtest/TestFilePreferencesWin.cpp
index b7d3a3159f25..c8766d4b973a 100644
--- a/xpcom/tests/gtest/TestFilePreferencesWin.cpp
+++ b/xpcom/tests/gtest/TestFilePreferencesWin.cpp
@@ -117,6 +117,10 @@ TEST(FilePreferencesWin, Normalization)
TEST(FilePreferencesWin, AccessUNC)
{
+ // gtest doesn't properly init Firefox, so we instantiate the whitelist here
+ // otherwise FilePreferences::IsBlockedUNCPath would always return false.
+ mozilla::FilePreferences::testing::AddDirectoryToWhitelist(NS_LITERAL_STRING("\\\\dummy"));
+
nsCOMPtr<nsIFile> lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
nsresult rv;
diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build
index 90b5fd7652e6..4f1c9c73d653 100644
--- a/xpcom/tests/gtest/moz.build
+++ b/xpcom/tests/gtest/moz.build
@@ -73,6 +73,11 @@ if CONFIG['OS_TARGET'] == 'WINNT':
UNIFIED_SOURCES += [
'TestFilePreferencesWin.cpp',
]
+else:
+ UNIFIED_SOURCES += [
+ 'TestFilePreferencesUnix.cpp',
+ ]
+
if CONFIG['WRAP_STL_INCLUDES'] and CONFIG['CC_TYPE'] != 'clang-cl':
UNIFIED_SOURCES += [