Unverified Commit 50f4653b authored by Georg Koppen's avatar Georg Koppen
Browse files

Backport of fix for bug 1412081

This patch is written by Valentin Gosu. It enhances our improvments for
dealing with file:// (see #24052 for a wider context)
parent e05cdec5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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",
+206 −122
Original line number Diff line number Diff line
@@ -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,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;
@@ -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;
@@ -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;
@@ -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;
  }
@@ -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;
@@ -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;
@@ -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);
}

+6 −0
Original line number Diff line number Diff line
@@ -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);
+58 −0
Original line number Diff line number Diff line
@@ -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) {
+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