[tor-commits] [tor-browser/tor-browser-60.1.0esr-8.0-1] Bug 1412081 - Add ability to blacklist file paths on Unix platforms. r=mayhemer, a=RyanVM

gk at torproject.org gk at torproject.org
Thu Aug 16 09:58:40 UTC 2018


commit dae353ff5f2d033b9b87ebf2a5f833c87068d4a3
Author: Valentin Gosu <valentin.gosu at 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 += [





More information about the tor-commits mailing list