Based on Adapted to apply cleanly to GNU IceCat. # HG changeset patch # User Honza Bambas # Date 1528830658 14400 # Node ID 431fa5dd4016bdab7e4bb0d3c4df85468fe337b0 # Parent e8e9e1ef79f2a18c61ec1b87cfb214c8d4960f8e Bug 1413868. r=valentin, a=RyanVM diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -4,16 +4,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/ipc/GeckoChildProcessHost.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" +#include "mozilla/FilePreferences.h" #include "mozilla/ChaosMode.h" #include "mozilla/IOInterposer.h" #include "mozilla/Likely.h" #include "mozilla/MemoryChecking.h" #include "mozilla/Poison.h" #include "mozilla/Preferences.h" #include "mozilla/ScopeExit.h" #include "mozilla/Services.h" @@ -4304,16 +4305,20 @@ XREMain::XRE_mainRun() // Need to write out the fact that the profile has been removed and potentially // that the selected/default profile changed. mProfileSvc->Flush(); } } mDirProvider.DoStartup(); + // As FilePreferences need the profile directory, we must initialize right here. + mozilla::FilePreferences::InitDirectoriesWhitelist(); + mozilla::FilePreferences::InitPrefs(); + OverrideDefaultLocaleIfNeeded(); #ifdef MOZ_CRASHREPORTER nsCString userAgentLocale; // Try a localized string first. This pref is always a localized string in // IceCatMobile, and might be elsewhere, too. if (NS_SUCCEEDED(Preferences::GetLocalizedCString("general.useragent.locale", &userAgentLocale))) { CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale); diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -46,16 +46,17 @@ #include "nsX11ErrorHandler.h" #include "nsGDKErrorHandler.h" #include "base/at_exit.h" #include "base/command_line.h" #include "base/message_loop.h" #include "base/process_util.h" #include "chrome/common/child_process.h" +#include "mozilla/FilePreferences.h" #include "mozilla/ipc/BrowserProcessSubThread.h" #include "mozilla/ipc/GeckoChildProcessHost.h" #include "mozilla/ipc/IOThreadChild.h" #include "mozilla/ipc/ProcessChild.h" #include "ScopedXREEmbed.h" #include "mozilla/plugins/PluginProcessChild.h" #include "mozilla/dom/ContentProcess.h" @@ -680,16 +681,18 @@ XRE_InitChildProcess(int aArgc, ::SetProcessShutdownParameters(0x280 - 1, SHUTDOWN_NORETRY); #endif #if defined(MOZ_SANDBOX) && defined(XP_WIN) // We need to do this after the process has been initialised, as // InitLoggingIfRequired may need access to prefs. mozilla::sandboxing::InitLoggingIfRequired(aChildData->ProvideLogFunction); #endif + mozilla::FilePreferences::InitDirectoriesWhitelist(); + mozilla::FilePreferences::InitPrefs(); OverrideDefaultLocaleIfNeeded(); #if defined(MOZ_CRASHREPORTER) #if defined(MOZ_CONTENT_SANDBOX) && !defined(MOZ_WIDGET_GONK) AddContentSandboxLevelAnnotation(); #endif #endif diff --git a/xpcom/io/FilePreferences.cpp b/xpcom/io/FilePreferences.cpp new file mode 100644 --- /dev/null +++ b/xpcom/io/FilePreferences.cpp @@ -0,0 +1,271 @@ +/* -*- 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" + +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 --- /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 --- a/xpcom/io/moz.build +++ b/xpcom/io/moz.build @@ -79,24 +79,26 @@ EXPORTS += [ 'nsUnicharInputStream.h', 'nsWildCard.h', 'SlicedInputStream.h', 'SpecialSystemDirectory.h', ] EXPORTS.mozilla += [ 'Base64.h', + 'FilePreferences.h', 'SnappyCompressOutputStream.h', 'SnappyFrameUtils.h', 'SnappyUncompressInputStream.h', ] UNIFIED_SOURCES += [ 'Base64.cpp', 'crc32c.c', + 'FilePreferences.cpp', 'nsAnonymousTemporaryFile.cpp', 'nsAppFileLocationProvider.cpp', 'nsBinaryStream.cpp', 'nsDirectoryService.cpp', 'nsEscape.cpp', 'nsInputStreamTee.cpp', 'nsIOUtil.cpp', 'nsLinebreakConverter.cpp', diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp --- a/xpcom/io/nsLocalFileWin.cpp +++ b/xpcom/io/nsLocalFileWin.cpp @@ -41,16 +41,17 @@ #include #include #include #include "nsXPIDLString.h" #include "prproces.h" #include "prlink.h" +#include "mozilla/FilePreferences.h" #include "mozilla/Mutex.h" #include "SpecialSystemDirectory.h" #include "nsTraceRefcnt.h" #include "nsXPCOMCIDInternal.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" @@ -1162,16 +1163,20 @@ nsLocalFile::InitWithPath(const nsAStrin char16_t secondChar = *(++begin); // just do a sanity check. if it has any forward slashes, it is not a Native path // on windows. Also, it must have a colon at after the first char. if (FindCharInReadable(L'/', begin, end)) { 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; } if (secondChar == L':') { // Make sure we have a valid drive, later code assumes the drive letter // is a single char a-z or A-Z. if (PathGetDriveNumberW(aFilePath.Data()) == -1) { @@ -1974,16 +1979,20 @@ nsLocalFile::CopySingleFile(nsIFile* aSo bool path1Remote, path2Remote; if (!IsRemoteFilePath(filePath.get(), path1Remote) || !IsRemoteFilePath(destPath.get(), path2Remote) || path1Remote || path2Remote) { 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); } else { copyOK = ::MoveFileExW(filePath.get(), destPath.get(), MOVEFILE_REPLACE_EXISTING); // Check if copying the source file to a different volume, diff --git a/xpcom/tests/gtest/TestFilePreferencesWin.cpp b/xpcom/tests/gtest/TestFilePreferencesWin.cpp new file mode 100644 --- /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 --- a/xpcom/tests/gtest/moz.build +++ b/xpcom/tests/gtest/moz.build @@ -51,16 +51,21 @@ UNIFIED_SOURCES += [ if CONFIG['MOZ_DEBUG'] and CONFIG['OS_ARCH'] not in ('WINNT') and CONFIG['OS_TARGET'] != 'Android': # FIXME bug 523392: TestDeadlockDetector doesn't like Windows # Bug 1054249: Doesn't work on Android UNIFIED_SOURCES += [ 'TestDeadlockDetector.cpp', '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', ] # Compile TestAllocReplacement separately so Windows headers don't pollute # the global namespace for other files. SOURCES += [