Loading dom/ipc/ContentPrefs.cpp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading xpcom/io/FilePreferences.cpp +206 −122 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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)); Loading @@ -43,122 +69,131 @@ 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 template <typename TChar> class Normalizer { public: Normalizer(const nsAString& aFilePath, const char16_t aSeparator); bool Get(nsAString& aNormalizedFilePath); private: bool ConsumeItem(); bool ConsumeSeparator(); bool IsEOF() { return mFilePathCursor == mFilePathEnd; } bool ConsumeName(); bool CheckParentDir(); bool CheckCurrentDir(); nsString::const_char_iterator mFilePathCursor; nsString::const_char_iterator mFilePathEnd; nsDependentSubstring mItem; char16_t const mSeparator; nsTArray<nsDependentSubstring> mStack; }; Normalizer::Normalizer(const nsAString& aFilePath, const char16_t aSeparator) Normalizer(const nsTSubstring<TChar>& aFilePath, const TChar aSeparator) : mFilePathCursor(aFilePath.BeginReading()) , mFilePathEnd(aFilePath.EndReading()) , mSeparator(aSeparator) { } bool Normalizer::ConsumeItem() bool Get(nsTSubstring<TChar>& aNormalizedFilePath) { if (IsEOF()) { return false; } aNormalizedFilePath.Truncate(); nsString::const_char_iterator nameBegin = mFilePathCursor; while (mFilePathCursor != mFilePathEnd) { if (*mFilePathCursor == mSeparator) { break; // don't include the separator // 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; } ++mFilePathCursor; if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); } #endif mItem.Rebind(nameBegin, mFilePathCursor); if (IsEOF()) { return true; } if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); } bool Normalizer::ConsumeSeparator() { if (IsEOF()) { while (!IsEOF()) { if (!ConsumeName()) { return false; } } if (*mFilePathCursor != mSeparator) { return false; for (auto const& name : mStack) { aNormalizedFilePath.Append(name); } ++mFilePathCursor; return true; } bool Normalizer::Get(nsAString& aNormalizedFilePath) private: bool ConsumeItem() { aNormalizedFilePath.Truncate(); if (IsEOF()) { return true; return false; } if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); typename nsTString<TChar>::const_char_iterator nameBegin = mFilePathCursor; while (mFilePathCursor != mFilePathEnd) { if (*mFilePathCursor == mSeparator) { break; // don't include the separator } ++mFilePathCursor; } if (IsEOF()) { mItem.Rebind(nameBegin, mFilePathCursor); return true; } if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); } while (!IsEOF()) { if (!ConsumeName()) { bool ConsumeSeparator() { if (IsEOF()) { return false; } } for (auto const& name : mStack) { aNormalizedFilePath.Append(name); if (*mFilePathCursor != mSeparator) { return false; } ++mFilePathCursor; return true; } bool Normalizer::ConsumeName() bool IsEOF() { return mFilePathCursor == mFilePathEnd; } bool ConsumeName() { if (!ConsumeItem()) { return true; Loading Loading @@ -191,9 +226,9 @@ bool Normalizer::ConsumeName() return true; } bool Normalizer::CheckCurrentDir() bool CheckParentDir() { if (mItem == NS_LITERAL_STRING(".")) { if (mItem.EqualsLiteral("..")) { ConsumeSeparator(); // EOF is acceptable return true; Loading @@ -202,9 +237,9 @@ bool Normalizer::CheckCurrentDir() return false; } bool Normalizer::CheckParentDir() bool CheckCurrentDir() { if (mItem == NS_LITERAL_STRING("..")) { if (mItem.EqualsLiteral(".")) { ConsumeSeparator(); // EOF is acceptable return true; Loading @@ -213,10 +248,22 @@ bool Normalizer::CheckParentDir() return false; } typename nsTString<TChar>::const_char_iterator mFilePathCursor; typename nsTString<TChar>::const_char_iterator mFilePathEnd; nsTDependentSubstring<TChar> mItem; TChar const mSeparator; nsTArray<nsTDependentSubstring<TChar>> mStack; }; } // anon bool IsBlockedUNCPath(const nsAString& aFilePath) { if (!sWhitelist) { return false; } if (!sBlockUNCPaths) { return false; } Loading @@ -226,12 +273,12 @@ bool IsBlockedUNCPath(const nsAString& aFilePath) } nsAutoString normalized; if (!Normalizer(aFilePath, L'\\').Get(normalized)) { if (!Normalizer<char16_t>(aFilePath, L'\\').Get(normalized)) { // Broken paths are considered invalid and thus inaccessible return true; } for (const auto& allowedPrefix : PathArray()) { for (const auto& allowedPrefix : PathWhitelist()) { if (StringBeginsWith(normalized, allowedPrefix)) { if (normalized.Length() == allowedPrefix.Length()) { return false; Loading @@ -251,6 +298,43 @@ 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) { // 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<char_path_t>(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; Loading @@ -258,12 +342,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) { Normalizer normalizer(aPath, L'\\'); Normalizer<char16_t> normalizer(aPath, L'\\'); return normalizer.Get(aNormalized); } Loading xpcom/io/FilePreferences.h +6 −0 Original line number Diff line number Diff line Loading @@ -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); Loading xpcom/io/nsLocalFileUnix.cpp +58 −0 Original line number Diff line number Diff line Loading @@ -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> Loading Loading @@ -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 */ Loading Loading @@ -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; } Loading Loading @@ -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) { Loading Loading @@ -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] == '/')) { Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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 | Loading Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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) { Loading xpcom/tests/gtest/TestFilePreferencesUnix.cpp 0 → 100644 +203 −0 Original line number Diff line number Diff line #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); } Loading
dom/ipc/ContentPrefs.cpp +1 −0 Original line number Diff line number Diff line Loading @@ -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", Loading
xpcom/io/FilePreferences.cpp +206 −122 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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)); Loading @@ -43,122 +69,131 @@ 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 template <typename TChar> class Normalizer { public: Normalizer(const nsAString& aFilePath, const char16_t aSeparator); bool Get(nsAString& aNormalizedFilePath); private: bool ConsumeItem(); bool ConsumeSeparator(); bool IsEOF() { return mFilePathCursor == mFilePathEnd; } bool ConsumeName(); bool CheckParentDir(); bool CheckCurrentDir(); nsString::const_char_iterator mFilePathCursor; nsString::const_char_iterator mFilePathEnd; nsDependentSubstring mItem; char16_t const mSeparator; nsTArray<nsDependentSubstring> mStack; }; Normalizer::Normalizer(const nsAString& aFilePath, const char16_t aSeparator) Normalizer(const nsTSubstring<TChar>& aFilePath, const TChar aSeparator) : mFilePathCursor(aFilePath.BeginReading()) , mFilePathEnd(aFilePath.EndReading()) , mSeparator(aSeparator) { } bool Normalizer::ConsumeItem() bool Get(nsTSubstring<TChar>& aNormalizedFilePath) { if (IsEOF()) { return false; } aNormalizedFilePath.Truncate(); nsString::const_char_iterator nameBegin = mFilePathCursor; while (mFilePathCursor != mFilePathEnd) { if (*mFilePathCursor == mSeparator) { break; // don't include the separator // 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; } ++mFilePathCursor; if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); } #endif mItem.Rebind(nameBegin, mFilePathCursor); if (IsEOF()) { return true; } if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); } bool Normalizer::ConsumeSeparator() { if (IsEOF()) { while (!IsEOF()) { if (!ConsumeName()) { return false; } } if (*mFilePathCursor != mSeparator) { return false; for (auto const& name : mStack) { aNormalizedFilePath.Append(name); } ++mFilePathCursor; return true; } bool Normalizer::Get(nsAString& aNormalizedFilePath) private: bool ConsumeItem() { aNormalizedFilePath.Truncate(); if (IsEOF()) { return true; return false; } if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); typename nsTString<TChar>::const_char_iterator nameBegin = mFilePathCursor; while (mFilePathCursor != mFilePathEnd) { if (*mFilePathCursor == mSeparator) { break; // don't include the separator } ++mFilePathCursor; } if (IsEOF()) { mItem.Rebind(nameBegin, mFilePathCursor); return true; } if (ConsumeSeparator()) { aNormalizedFilePath.Append(mSeparator); } while (!IsEOF()) { if (!ConsumeName()) { bool ConsumeSeparator() { if (IsEOF()) { return false; } } for (auto const& name : mStack) { aNormalizedFilePath.Append(name); if (*mFilePathCursor != mSeparator) { return false; } ++mFilePathCursor; return true; } bool Normalizer::ConsumeName() bool IsEOF() { return mFilePathCursor == mFilePathEnd; } bool ConsumeName() { if (!ConsumeItem()) { return true; Loading Loading @@ -191,9 +226,9 @@ bool Normalizer::ConsumeName() return true; } bool Normalizer::CheckCurrentDir() bool CheckParentDir() { if (mItem == NS_LITERAL_STRING(".")) { if (mItem.EqualsLiteral("..")) { ConsumeSeparator(); // EOF is acceptable return true; Loading @@ -202,9 +237,9 @@ bool Normalizer::CheckCurrentDir() return false; } bool Normalizer::CheckParentDir() bool CheckCurrentDir() { if (mItem == NS_LITERAL_STRING("..")) { if (mItem.EqualsLiteral(".")) { ConsumeSeparator(); // EOF is acceptable return true; Loading @@ -213,10 +248,22 @@ bool Normalizer::CheckParentDir() return false; } typename nsTString<TChar>::const_char_iterator mFilePathCursor; typename nsTString<TChar>::const_char_iterator mFilePathEnd; nsTDependentSubstring<TChar> mItem; TChar const mSeparator; nsTArray<nsTDependentSubstring<TChar>> mStack; }; } // anon bool IsBlockedUNCPath(const nsAString& aFilePath) { if (!sWhitelist) { return false; } if (!sBlockUNCPaths) { return false; } Loading @@ -226,12 +273,12 @@ bool IsBlockedUNCPath(const nsAString& aFilePath) } nsAutoString normalized; if (!Normalizer(aFilePath, L'\\').Get(normalized)) { if (!Normalizer<char16_t>(aFilePath, L'\\').Get(normalized)) { // Broken paths are considered invalid and thus inaccessible return true; } for (const auto& allowedPrefix : PathArray()) { for (const auto& allowedPrefix : PathWhitelist()) { if (StringBeginsWith(normalized, allowedPrefix)) { if (normalized.Length() == allowedPrefix.Length()) { return false; Loading @@ -251,6 +298,43 @@ 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) { // 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<char_path_t>(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; Loading @@ -258,12 +342,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) { Normalizer normalizer(aPath, L'\\'); Normalizer<char16_t> normalizer(aPath, L'\\'); return normalizer.Get(aNormalized); } Loading
xpcom/io/FilePreferences.h +6 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
xpcom/io/nsLocalFileUnix.cpp +58 −0 Original line number Diff line number Diff line Loading @@ -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> Loading Loading @@ -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 */ Loading Loading @@ -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; } Loading Loading @@ -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) { Loading Loading @@ -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] == '/')) { Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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 | Loading Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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) { Loading Loading @@ -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) { Loading
xpcom/tests/gtest/TestFilePreferencesUnix.cpp 0 → 100644 +203 −0 Original line number Diff line number Diff line #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); }