From 5bd326c5f52501fe535b27588ada99226b67c3f1 Mon Sep 17 00:00:00 2001 From: Valentin Gosu Date: Wed, 20 Jun 2018 02:52:12 +0200 Subject: [PATCH] Bug 1412081 - Add ability to blacklist file paths on Unix platforms r=mayhemer --HG-- extra : rebase_source : 6894f5c3df745519e5e9db5b7bf6f004922152d1 --- xpcom/io/FilePreferences.cpp | 277 ++++++++++++------ xpcom/io/FilePreferences.h | 6 + xpcom/io/nsLocalFileUnix.cpp | 58 ++++ xpcom/tests/gtest/TestFilePreferencesUnix.cpp | 203 +++++++++++++ xpcom/tests/gtest/moz.build | 5 + 5 files changed, 453 insertions(+), 96 deletions(-) create mode 100644 xpcom/tests/gtest/TestFilePreferencesUnix.cpp diff --git a/xpcom/io/FilePreferences.cpp b/xpcom/io/FilePreferences.cpp index 5f23f3f9a3ee..9ef2827ff16d 100644 --- a/xpcom/io/FilePreferences.cpp +++ b/xpcom/io/FilePreferences.cpp @@ -6,25 +6,46 @@ #include "FilePreferences.h" +#include "mozilla/ClearOnShutdown.h" #include "mozilla/Preferences.h" +#include "mozilla/StaticPtr.h" #include "mozilla/Tokenizer.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" +#include "nsString.h" namespace mozilla { namespace FilePreferences { static bool sBlockUNCPaths = false; -typedef nsTArray Paths; +typedef nsTArray WinPaths; -static Paths& PathArray() +static WinPaths& PathWhitelist() { - static Paths sPaths; + static WinPaths sPaths; return sPaths; } -static void AllowDirectory(char const* directory) +#ifdef XP_WIN +typedef char16_t char_path_t; +#else +typedef char char_path_t; +#endif + +typedef nsTArray> Paths; +static StaticAutoPtr sBlacklist; + +static Paths& PathBlacklist() +{ + if (!sBlacklist) { + sBlacklist = new nsTArray>(); + ClearOnShutdown(&sBlacklist); + } + return *sBlacklist; +} + +static void AllowUNCDirectory(char const* directory) { nsCOMPtr file; NS_GetSpecialDirectory(directory, getter_AddRefs(file)); @@ -44,135 +65,160 @@ 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(); + nsTAutoString blacklist; +#ifdef XP_WIN + Preferences::GetString("network.file.path_blacklist", blacklist); +#else + Preferences::GetCString("network.file.path_blacklist", blacklist); +#endif + + TTokenizer p(blacklist); + while (!p.CheckEOF()) { + nsTString path; + Unused << p.ReadUntil(TTokenizer::Token::Char(','), path); + path.Trim(" "); + if (!path.IsEmpty()) { + PathBlacklist().AppendElement(path); + } + 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 : public Tokenizer16 +template +class TNormalizer + : public TTokenizer { + typedef TTokenizer base; public: - Normalizer(const nsAString& aFilePath, const Token& aSeparator); - bool Get(nsAString& aNormalizedFilePath); + typedef typename base::Token Token; + + TNormalizer(const nsTSubstring& aFilePath, const Token& aSeparator) + : TTokenizer(aFilePath) + , mSeparator(aSeparator) + { + } + + bool Get(nsTSubstring& 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 (base::Check(mSeparator)) { + aNormalizedFilePath.Append(mSeparator.AsChar()); + } +#endif + + if (base::Check(mSeparator)) { + aNormalizedFilePath.Append(mSeparator.AsChar()); + } + + while (base::HasInput()) { + if (!ConsumeName()) { + return false; + } + } + + for (auto const& name : mStack) { + aNormalizedFilePath.Append(name); + } + + return true; + } + private: - bool ConsumeName(); - bool CheckParentDir(); - bool CheckCurrentDir(); - bool CheckSeparator(); - - Token const mSeparator; - nsTArray mStack; -}; - -Normalizer::Normalizer(const nsAString& aFilePath, const Token& aSeparator) - : Tokenizer16(aFilePath) - , mSeparator(aSeparator) -{ -} - -bool Normalizer::Get(nsAString& aNormalizedFilePath) -{ - aNormalizedFilePath.Truncate(); - - if (Check(mSeparator)) { - aNormalizedFilePath.Append(mSeparator.AsChar()); - } - if (Check(mSeparator)) { - aNormalizedFilePath.Append(mSeparator.AsChar()); - } - - while (HasInput()) { - if (!ConsumeName()) { - return false; - } - } - - for (auto const& name : mStack) { - aNormalizedFilePath.Append(name); - } - - return true; -} - -bool Normalizer::ConsumeName() -{ - if (CheckEOF()) { - return true; - } - - if (CheckCurrentDir()) { - return true; - } - - if (CheckParentDir()) { - if (!mStack.Length()) { - // This means there are more \.. than valid names - return false; + bool ConsumeName() + { + if (base::CheckEOF()) { + return true; } - mStack.RemoveLastElement(); + if (CheckCurrentDir()) { + return true; + } + + if (CheckParentDir()) { + if (!mStack.Length()) { + // This means there are more \.. than valid names + return false; + } + + mStack.RemoveLastElement(); + return true; + } + + nsTDependentSubstring name; + if (base::ReadUntil(mSeparator, name, base::INCLUDE_LAST) && name.Length() == 1) { + // this means and empty name (a lone slash), which is illegal + return false; + } + mStack.AppendElement(name); + return true; } - nsDependentSubstring name; - if (ReadUntil(mSeparator, name, INCLUDE_LAST) && name.Length() == 1) { - // this means and empty name (a lone slash), which is illegal + bool CheckParentDir() + { + typename nsTString::const_char_iterator cursor = base::mCursor; + if (base::CheckChar('.') && base::CheckChar('.') && CheckSeparator()) { + return true; + } + + base::mCursor = cursor; return false; } - mStack.AppendElement(name); - return true; -} + bool CheckCurrentDir() + { + typename nsTString::const_char_iterator cursor = base::mCursor; + if (base::CheckChar('.') && CheckSeparator()) { + return true; + } -bool Normalizer::CheckCurrentDir() -{ - nsString::const_char_iterator cursor = mCursor; - if (CheckChar('.') && CheckSeparator()) { - return true; + base::mCursor = cursor; + return false; } - mCursor = cursor; - return false; -} - -bool Normalizer::CheckParentDir() -{ - nsString::const_char_iterator cursor = mCursor; - if (CheckChar('.') && CheckChar('.') && CheckSeparator()) { - return true; + bool CheckSeparator() + { + return base::Check(mSeparator) || base::CheckEOF(); } - mCursor = cursor; - return false; -} - -bool Normalizer::CheckSeparator() -{ - return Check(mSeparator) || CheckEOF(); -} + Token const mSeparator; + nsTArray> mStack; +}; } // anon bool IsBlockedUNCPath(const nsAString& aFilePath) { + typedef TNormalizer Normalizer; if (!sBlockUNCPaths) { return false; } @@ -187,7 +233,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; @@ -207,6 +253,44 @@ bool IsBlockedUNCPath(const nsAString& aFilePath) return true; } +#ifdef XP_WIN +const char kPathSeparator = '\\'; +#else +const char kPathSeparator = '/'; +#endif + +bool IsAllowedPath(const nsTSubstring& aFilePath) +{ + typedef TNormalizer 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 normalized; + if (!Normalizer(aFilePath, Normalizer::Token::Char(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; @@ -214,11 +298,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 Normalizer; Normalizer normalizer(aPath, Normalizer::Token::Char('\\')); 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 f0d9f6d72bbd..30fe84233adf 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 #include @@ -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 */ @@ -139,6 +142,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; } @@ -270,6 +280,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) { @@ -312,6 +327,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] == '/')) { @@ -325,6 +345,10 @@ nsLocalFile::InitWithNativePath(const nsACString& aFilePath) NS_IMETHODIMP nsLocalFile::CreateAllAncestors(uint32_t aPermissions) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + // I promise to play nice char* buffer = mPath.BeginWriting(); char* slashp = buffer; @@ -396,6 +420,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(); @@ -417,6 +444,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; @@ -443,6 +473,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; } @@ -492,6 +526,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 | @@ -547,6 +585,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. @@ -1011,6 +1053,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) { @@ -1953,6 +1999,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 giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); if (!giovfs) { @@ -1996,6 +2046,10 @@ nsLocalFile::Reveal() NS_IMETHODIMP nsLocalFile::Launch() { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + #ifdef MOZ_WIDGET_GTK nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); if (!giovfs) { @@ -2150,6 +2204,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 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 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 dirEnumerator; + rv = blacklistedDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + nsCOMPtr 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/moz.build b/xpcom/tests/gtest/moz.build index 85498f0bad46..2d778fd3ba82 100644 --- a/xpcom/tests/gtest/moz.build +++ b/xpcom/tests/gtest/moz.build @@ -76,6 +76,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 += [