From fdc6f820ca5e4b2832a0208e2a183fb1d6f8b45f Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 30 Jun 2018 18:34:19 +0200 Subject: Bug 1413868. --- xpcom/io/FilePreferences.cpp | 272 +++++++++++++++++++++++++++ xpcom/io/FilePreferences.h | 25 +++ xpcom/io/moz.build | 5 + xpcom/io/nsLocalFileWin.cpp | 9 + xpcom/tests/gtest/TestFilePreferencesWin.cpp | 141 ++++++++++++++ xpcom/tests/gtest/moz.build | 5 + 6 files changed, 457 insertions(+) create mode 100644 xpcom/io/FilePreferences.cpp create mode 100644 xpcom/io/FilePreferences.h create mode 100644 xpcom/tests/gtest/TestFilePreferencesWin.cpp (limited to 'xpcom') diff --git a/xpcom/io/FilePreferences.cpp b/xpcom/io/FilePreferences.cpp new file mode 100644 index 000000000..ef942beb2 --- /dev/null +++ b/xpcom/io/FilePreferences.cpp @@ -0,0 +1,272 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilePreferences.h" + +#include "mozilla/Preferences.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsString.h" + +namespace mozilla { +namespace FilePreferences { + +static bool sBlockUNCPaths = false; +typedef nsTArray Paths; + +static Paths& PathArray() +{ + static Paths sPaths; + return sPaths; +} + +static void AllowDirectory(char const* directory) +{ + nsCOMPtr file; + NS_GetSpecialDirectory(directory, getter_AddRefs(file)); + if (!file) { + return; + } + + nsString path; + if (NS_FAILED(file->GetTarget(path))) { + return; + } + + // The whitelist makes sense only for UNC paths, because this code is used + // to block only UNC paths, hence, no need to add non-UNC directories here + // as those would never pass the check. + if (!StringBeginsWith(path, NS_LITERAL_STRING("\\\\"))) { + return; + } + + if (!PathArray().Contains(path)) { + PathArray().AppendElement(path); + } +} + +void InitPrefs() +{ + sBlockUNCPaths = Preferences::GetBool("network.file.disable_unc_paths", false); +} + +void InitDirectoriesWhitelist() +{ + // NS_GRE_DIR is the installation path where the binary resides. + AllowDirectory(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); +} + +namespace { // anon + +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 mStack; +}; + +Normalizer::Normalizer(const nsAString& aFilePath, const char16_t aSeparator) + : mFilePathCursor(aFilePath.BeginReading()) + , mFilePathEnd(aFilePath.EndReading()) + , mSeparator(aSeparator) +{ +} + +bool Normalizer::ConsumeItem() +{ + if (IsEOF()) { + return false; + } + + nsString::const_char_iterator nameBegin = mFilePathCursor; + while (mFilePathCursor != mFilePathEnd) { + if (*mFilePathCursor == mSeparator) { + break; // don't include the separator + } + ++mFilePathCursor; + } + + mItem.Rebind(nameBegin, mFilePathCursor); + return true; +} + +bool Normalizer::ConsumeSeparator() +{ + if (IsEOF()) { + return false; + } + + if (*mFilePathCursor != mSeparator) { + return false; + } + + ++mFilePathCursor; + return true; +} + +bool Normalizer::Get(nsAString& aNormalizedFilePath) +{ + aNormalizedFilePath.Truncate(); + + if (IsEOF()) { + return true; + } + if (ConsumeSeparator()) { + aNormalizedFilePath.Append(mSeparator); + } + + if (IsEOF()) { + return true; + } + if (ConsumeSeparator()) { + aNormalizedFilePath.Append(mSeparator); + } + + while (!IsEOF()) { + if (!ConsumeName()) { + return false; + } + } + + for (auto const& name : mStack) { + aNormalizedFilePath.Append(name); + } + + return true; +} + +bool Normalizer::ConsumeName() +{ + if (!ConsumeItem()) { + return true; + } + + if (CheckCurrentDir()) { + return true; + } + + if (CheckParentDir()) { + if (!mStack.Length()) { + // This means there are more \.. than valid names + return false; + } + + mStack.RemoveElementAt(mStack.Length() - 1); + return true; + } + + if (mItem.IsEmpty()) { + // this means an empty name (a lone slash), which is illegal + return false; + } + + if (ConsumeSeparator()) { + mItem.Rebind(mItem.BeginReading(), mFilePathCursor); + } + mStack.AppendElement(mItem); + + return true; +} + +bool Normalizer::CheckCurrentDir() +{ + if (mItem == NS_LITERAL_STRING(".")) { + ConsumeSeparator(); + // EOF is acceptable + return true; + } + + return false; +} + +bool Normalizer::CheckParentDir() +{ + if (mItem == NS_LITERAL_STRING("..")) { + ConsumeSeparator(); + // EOF is acceptable + return true; + } + + return false; +} + +} // anon + +bool IsBlockedUNCPath(const nsAString& aFilePath) +{ + if (!sBlockUNCPaths) { + return false; + } + + if (!StringBeginsWith(aFilePath, NS_LITERAL_STRING("\\\\"))) { + return false; + } + + nsAutoString normalized; + if (!Normalizer(aFilePath, L'\\').Get(normalized)) { + // Broken paths are considered invalid and thus inaccessible + return true; + } + + for (const auto& allowedPrefix : PathArray()) { + if (StringBeginsWith(normalized, allowedPrefix)) { + if (normalized.Length() == allowedPrefix.Length()) { + return false; + } + if (normalized[allowedPrefix.Length()] == L'\\') { + return false; + } + + // When we are here, the path has a form "\\path\prefixevil" + // while we have an allowed prefix of "\\path\prefix". + // Note that we don't want to add a slash to the end of a prefix + // so that opening the directory (no slash at the end) still works. + break; + } + } + + return true; +} + +void testing::SetBlockUNCPaths(bool aBlock) +{ + sBlockUNCPaths = aBlock; +} + +void testing::AddDirectoryToWhitelist(nsAString const & aPath) +{ + PathArray().AppendElement(aPath); +} + +bool testing::NormalizePath(nsAString const & aPath, nsAString & aNormalized) +{ + Normalizer normalizer(aPath, L'\\'); + return normalizer.Get(aNormalized); +} + +} // ::FilePreferences +} // ::mozilla diff --git a/xpcom/io/FilePreferences.h b/xpcom/io/FilePreferences.h new file mode 100644 index 000000000..fa281f9e6 --- /dev/null +++ b/xpcom/io/FilePreferences.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIObserver.h" + +namespace mozilla { +namespace FilePreferences { + +void InitPrefs(); +void InitDirectoriesWhitelist(); +bool IsBlockedUNCPath(const nsAString& aFilePath); + +namespace testing { + +void SetBlockUNCPaths(bool aBlock); +void AddDirectoryToWhitelist(nsAString const& aPath); +bool NormalizePath(nsAString const & aPath, nsAString & aNormalized); + +} + +} // FilePreferences +} // mozilla diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build index 6f21e0a72..fdefa841b 100644 --- a/xpcom/io/moz.build +++ b/xpcom/io/moz.build @@ -84,6 +84,7 @@ EXPORTS += [ EXPORTS.mozilla += [ 'Base64.h', + 'FilePreferences.h', 'SnappyCompressOutputStream.h', 'SnappyFrameUtils.h', 'SnappyUncompressInputStream.h', @@ -119,6 +120,10 @@ UNIFIED_SOURCES += [ 'SpecialSystemDirectory.cpp', ] +SOURCES += [ + 'FilePreferences.cpp', +] + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': SOURCES += [ 'CocoaFileUtils.mm', diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp index 66e267807..5a72c750c 100644 --- a/xpcom/io/nsLocalFileWin.cpp +++ b/xpcom/io/nsLocalFileWin.cpp @@ -45,6 +45,7 @@ #include "prproces.h" #include "prlink.h" +#include "mozilla/FilePreferences.h" #include "mozilla/Mutex.h" #include "SpecialSystemDirectory.h" @@ -1166,6 +1167,10 @@ nsLocalFile::InitWithPath(const nsAString& aFilePath) return NS_ERROR_FILE_UNRECOGNIZED_PATH; } + if (FilePreferences::IsBlockedUNCPath(aFilePath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) { return NS_ERROR_FILE_UNRECOGNIZED_PATH; } @@ -1976,6 +1981,10 @@ nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent, dwCopyFlags |= COPY_FILE_NO_BUFFERING; } + if (FilePreferences::IsBlockedUNCPath(destPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + if (!move) { copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr, nullptr, dwCopyFlags); diff --git a/xpcom/tests/gtest/TestFilePreferencesWin.cpp b/xpcom/tests/gtest/TestFilePreferencesWin.cpp new file mode 100644 index 000000000..b7d3a3159 --- /dev/null +++ b/xpcom/tests/gtest/TestFilePreferencesWin.cpp @@ -0,0 +1,141 @@ +#include "gtest/gtest.h" + +#include "mozilla/FilePreferences.h" +#include "nsIFile.h" +#include "nsXPCOMCID.h" + +TEST(FilePreferencesWin, Normalization) +{ + nsAutoString normalized; + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("foo"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("foo")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\foo"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\foo")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("foo\\some"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("foo\\some")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\.\\foo"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\."), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\.\\"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\.\\."), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo\\bar")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo\\bar\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\."), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo\\bar\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\.\\"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo\\bar\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\..\\"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\.."), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\foo\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\..\\bar\\..\\"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\..\\bar"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\bar")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\..\\..\\"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\")); + + mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\.\\..\\.\\..\\"), normalized); + ASSERT_TRUE(normalized == NS_LITERAL_STRING("\\\\")); + + bool result; + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\.."), normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\..\\"), normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\.\\..\\"), normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\\\bar"), normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\foo\\bar\\..\\..\\..\\..\\"), normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\\\"), normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\.\\\\"), normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + NS_LITERAL_STRING("\\\\..\\\\"), normalized); + ASSERT_FALSE(result); +} + +TEST(FilePreferencesWin, AccessUNC) +{ + nsCOMPtr lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + + nsresult rv; + + mozilla::FilePreferences::testing::SetBlockUNCPaths(false); + + rv = lf->InitWithPath(NS_LITERAL_STRING("\\\\nice\\..\\evil\\share")); + ASSERT_EQ(rv, NS_OK); + + mozilla::FilePreferences::testing::SetBlockUNCPaths(true); + + rv = lf->InitWithPath(NS_LITERAL_STRING("\\\\nice\\..\\evil\\share")); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + mozilla::FilePreferences::testing::AddDirectoryToWhitelist(NS_LITERAL_STRING("\\\\nice")); + + rv = lf->InitWithPath(NS_LITERAL_STRING("\\\\nice\\share")); + ASSERT_EQ(rv, NS_OK); + + rv = lf->InitWithPath(NS_LITERAL_STRING("\\\\nice\\..\\evil\\share")); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); +} diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build index 53836eaef..ac98c2217 100644 --- a/xpcom/tests/gtest/moz.build +++ b/xpcom/tests/gtest/moz.build @@ -56,6 +56,11 @@ if CONFIG['MOZ_DEBUG'] and CONFIG['OS_ARCH'] not in ('WINNT') and CONFIG['OS_TAR 'TestDeadlockDetectorScalability.cpp', ] +if CONFIG['OS_TARGET'] == 'WINNT': + UNIFIED_SOURCES += [ + 'TestFilePreferencesWin.cpp', + ] + if CONFIG['WRAP_STL_INCLUDES'] and not CONFIG['CLANG_CL']: UNIFIED_SOURCES += [ 'TestSTLWrappers.cpp', -- cgit v1.2.3