commit dae353ff5f2d033b9b87ebf2a5f833c87068d4a3 Author: Valentin Gosu valentin.gosu@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 += [
tbb-commits@lists.torproject.org