diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 37d846ad0108..6aab9ce59560 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -42,6 +42,7 @@ #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/DOMTypes.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/FileSystemSecurity.h" #include "mozilla/dom/FileBlobImpl.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLTemplateElement.h" @@ -7885,6 +7886,19 @@ nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable, continue; } + if (aParent) { + bool isDir = false; + if (NS_SUCCEEDED(file->IsDirectory(&isDir)) && isDir) { + nsAutoString path; + if (NS_WARN_IF(NS_FAILED(file->GetPath(path)))) { + continue; + } + + RefPtr fss = FileSystemSecurity::GetOrCreate(); + fss->GrantAccessToContentProcess(aParent->ChildID(), path); + } + } + blobImpl = new FileBlobImpl(file); IgnoredErrorResult rv; diff --git a/dom/filesystem/FileSystemRequestParent.cpp b/dom/filesystem/FileSystemRequestParent.cpp index 25125b65dfb4..af4359b84436 100644 --- a/dom/filesystem/FileSystemRequestParent.cpp +++ b/dom/filesystem/FileSystemRequestParent.cpp @@ -10,8 +10,12 @@ #include "GetDirectoryListingTask.h" #include "GetFileOrDirectoryTask.h" +#include "mozilla/dom/ContentParent.h" #include "mozilla/dom/FileSystemBase.h" +#include "mozilla/dom/FileSystemSecurity.h" #include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/Unused.h" +#include "nsProxyRelease.h" using namespace mozilla::ipc; @@ -69,14 +73,104 @@ FileSystemRequestParent::Initialize(const FileSystemParams& aParams) return true; } +namespace { + +class CheckPermissionRunnable final : public Runnable +{ +public: + CheckPermissionRunnable(already_AddRefed aParent, + FileSystemRequestParent* aActor, + FileSystemTaskParentBase* aTask, + const nsAString& aPath) + : mContentParent(aParent) + , mActor(aActor) + , mTask(aTask) + , mPath(aPath) + , mBackgroundEventTarget(NS_GetCurrentThread()) + { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MOZ_ASSERT(mContentParent); + MOZ_ASSERT(mActor); + MOZ_ASSERT(mTask); + MOZ_ASSERT(mBackgroundEventTarget); + } + + NS_IMETHOD + Run() override + { + if (NS_IsMainThread()) { + auto raii = mozilla::MakeScopeExit([&] { mContentParent = nullptr; }); + + + if (!mozilla::Preferences::GetBool("dom.filesystem.pathcheck.disabled", false)) { + RefPtr fss = FileSystemSecurity::Get(); + if (NS_WARN_IF(!fss || + !fss->ContentProcessHasAccessTo(mContentParent->ChildID(), + mPath))) { + mContentParent->KillHard("This path is not allowed."); + return NS_OK; + } + } + + return mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + } + + AssertIsOnBackgroundThread(); + + // It can happen that this actor has been destroyed in the meantime we were + // on the main-thread. + if (!mActor->Destroyed()) { + mTask->Start(); + } + + return NS_OK; + } + +private: + ~CheckPermissionRunnable() + { + NS_ProxyRelease(mBackgroundEventTarget, mActor.forget()); + } + + RefPtr mContentParent; + RefPtr mActor; + RefPtr mTask; + const nsString mPath; + + nsCOMPtr mBackgroundEventTarget; +}; + +} // anonymous + void FileSystemRequestParent::Start() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mDestroyed); MOZ_ASSERT(mFileSystem); MOZ_ASSERT(mTask); - mTask->Start(); + nsAutoString path; + if (NS_WARN_IF(NS_FAILED(mTask->GetTargetPath(path)))) { + Unused << Send__delete__(this, FileSystemErrorResponse(NS_ERROR_DOM_SECURITY_ERR)); + return; + } + + RefPtr parent = BackgroundParent::GetContentParent(Manager()); + + // If the ContentParent is null we are dealing with a same-process actor. + if (!parent) { + mTask->Start(); + return; + } + + RefPtr runnable = + new CheckPermissionRunnable(parent.forget(), this, mTask, path); + NS_DispatchToMainThread(runnable); } void diff --git a/dom/filesystem/FileSystemSecurity.cpp b/dom/filesystem/FileSystemSecurity.cpp new file mode 100644 index 000000000000..56f8a74e8013 --- /dev/null +++ b/dom/filesystem/FileSystemSecurity.cpp @@ -0,0 +1,107 @@ +/* -*- 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 "FileSystemSecurity.h" +#include "FileSystemUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace dom { + +namespace { + +StaticRefPtr gFileSystemSecurity; + +} // anonymous + +/* static */ already_AddRefed +FileSystemSecurity::Get() +{ + MOZ_ASSERT(NS_IsMainThread()); + AssertIsInMainProcess(); + + RefPtr service = gFileSystemSecurity.get(); + return service.forget(); +} + +/* static */ already_AddRefed +FileSystemSecurity::GetOrCreate() +{ + MOZ_ASSERT(NS_IsMainThread()); + AssertIsInMainProcess(); + + if (!gFileSystemSecurity) { + gFileSystemSecurity = new FileSystemSecurity(); + ClearOnShutdown(&gFileSystemSecurity); + } + + RefPtr service = gFileSystemSecurity.get(); + return service.forget(); +} + +FileSystemSecurity::FileSystemSecurity() +{ + MOZ_ASSERT(NS_IsMainThread()); + AssertIsInMainProcess(); +} + +FileSystemSecurity::~FileSystemSecurity() +{ + MOZ_ASSERT(NS_IsMainThread()); + AssertIsInMainProcess(); +} + +void +FileSystemSecurity::GrantAccessToContentProcess(ContentParentId aId, + const nsAString& aDirectoryPath) +{ + MOZ_ASSERT(NS_IsMainThread()); + AssertIsInMainProcess(); + + nsTArray* paths; + if (!mPaths.Get(aId, &paths)) { + paths = new nsTArray(); + mPaths.Put(aId, paths); + } else if (paths->Contains(aDirectoryPath)) { + return; + } + + paths->AppendElement(aDirectoryPath); +} + +void +FileSystemSecurity::Forget(ContentParentId aId) +{ + MOZ_ASSERT(NS_IsMainThread()); + AssertIsInMainProcess(); + + mPaths.Remove(aId); +} + +bool +FileSystemSecurity::ContentProcessHasAccessTo(ContentParentId aId, + const nsAString& aPath) +{ + MOZ_ASSERT(NS_IsMainThread()); + AssertIsInMainProcess(); + + nsTArray* paths; + if (!mPaths.Get(aId, &paths)) { + return false; + } + + for (uint32_t i = 0, len = paths->Length(); i < len; ++i) { + if (FileSystemUtils::IsDescendantPath(paths->ElementAt(i), aPath)) { + return true; + } + } + + return false; +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/filesystem/FileSystemSecurity.h b/dom/filesystem/FileSystemSecurity.h new file mode 100644 index 000000000000..fe61dc7c6dc6 --- /dev/null +++ b/dom/filesystem/FileSystemSecurity.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_FileSystemSecurity_h +#define mozilla_dom_FileSystemSecurity_h + +#include "mozilla/dom/ipc/IdType.h" +#include "nsClassHashtable.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace dom { + +class FileSystemSecurity final +{ +public: + NS_INLINE_DECL_REFCOUNTING(FileSystemSecurity) + + static already_AddRefed + Get(); + + static already_AddRefed + GetOrCreate(); + + void + GrantAccessToContentProcess(ContentParentId aId, + const nsAString& aDirectoryPath); + + void + Forget(ContentParentId aId); + + bool + ContentProcessHasAccessTo(ContentParentId aId, const nsAString& aPath); + +private: + FileSystemSecurity(); + ~FileSystemSecurity(); + + nsClassHashtable> mPaths; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FileSystemSecurity_h diff --git a/dom/filesystem/FileSystemTaskBase.h b/dom/filesystem/FileSystemTaskBase.h index 03e9f2090c27..0435587711f8 100644 --- a/dom/filesystem/FileSystemTaskBase.h +++ b/dom/filesystem/FileSystemTaskBase.h @@ -231,6 +231,9 @@ public: NS_IMETHOD Run() override; + virtual nsresult + GetTargetPath(nsAString& aPath) const = 0; + private: /* * Wrap the task result to FileSystemResponseValue for sending it through IPC. diff --git a/dom/filesystem/FileSystemUtils.cpp b/dom/filesystem/FileSystemUtils.cpp index 490c1f810820..872e049ee1cc 100644 --- a/dom/filesystem/FileSystemUtils.cpp +++ b/dom/filesystem/FileSystemUtils.cpp @@ -20,24 +20,12 @@ TokenizerIgnoreNothing(char16_t /* aChar */) } // anonymous namespace /* static */ bool -FileSystemUtils::IsDescendantPath(nsIFile* aFile, - nsIFile* aDescendantFile) +FileSystemUtils::IsDescendantPath(const nsAString& aPath, + const nsAString& aDescendantPath) { - nsAutoString path; - nsresult rv = aFile->GetPath(path); - if (NS_WARN_IF(NS_FAILED(rv))) { - return false; - } - - nsAutoString descendantPath; - rv = aDescendantFile->GetPath(descendantPath); - if (NS_WARN_IF(NS_FAILED(rv))) { - return false; - } - // Check the sub-directory path to see if it has the parent path as prefix. - if (descendantPath.Length() <= path.Length() || - !StringBeginsWith(descendantPath, path)) { + if (!aDescendantPath.Equals(aPath) && + !StringBeginsWith(aDescendantPath, aPath)) { return false; } diff --git a/dom/filesystem/FileSystemUtils.h b/dom/filesystem/FileSystemUtils.h index 2285bdc1c56f..ae302e07ca6c 100644 --- a/dom/filesystem/FileSystemUtils.h +++ b/dom/filesystem/FileSystemUtils.h @@ -26,7 +26,8 @@ public: * Return true if aDescendantPath is a descendant of aPath. */ static bool - IsDescendantPath(nsIFile* aPath, nsIFile* aDescendantPath); + IsDescendantPath(const nsAString& aPath, + const nsAString& aDescendantPath); /** * Return true if this is valid DOMPath. It also splits the path in diff --git a/dom/filesystem/GetDirectoryListingTask.cpp b/dom/filesystem/GetDirectoryListingTask.cpp index 25ca8685ffc1..081e3f354b6c 100644 --- a/dom/filesystem/GetDirectoryListingTask.cpp +++ b/dom/filesystem/GetDirectoryListingTask.cpp @@ -380,5 +380,11 @@ GetDirectoryListingTaskParent::IOWork() return NS_OK; } +nsresult +GetDirectoryListingTaskParent::GetTargetPath(nsAString& aPath) const +{ + return mTargetPath->GetPath(aPath); +} + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/GetDirectoryListingTask.h b/dom/filesystem/GetDirectoryListingTask.h index 764ce478919b..7692c8bae54d 100644 --- a/dom/filesystem/GetDirectoryListingTask.h +++ b/dom/filesystem/GetDirectoryListingTask.h @@ -69,6 +69,9 @@ public: FileSystemRequestParent* aParent, ErrorResult& aRv); + nsresult + GetTargetPath(nsAString& aPath) const override; + private: GetDirectoryListingTaskParent(FileSystemBase* aFileSystem, const FileSystemGetDirectoryListingParams& aParam, diff --git a/dom/filesystem/GetFileOrDirectoryTask.cpp b/dom/filesystem/GetFileOrDirectoryTask.cpp index 7f721bc63375..15d0baf88d38 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.cpp +++ b/dom/filesystem/GetFileOrDirectoryTask.cpp @@ -270,5 +270,11 @@ GetFileOrDirectoryTaskParent::IOWork() return NS_OK; } +nsresult +GetFileOrDirectoryTaskParent::GetTargetPath(nsAString& aPath) const +{ + return mTargetPath->GetPath(aPath); +} + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/GetFileOrDirectoryTask.h b/dom/filesystem/GetFileOrDirectoryTask.h index 28a9fc9354f2..2a53df158fad 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.h +++ b/dom/filesystem/GetFileOrDirectoryTask.h @@ -62,6 +62,9 @@ public: FileSystemRequestParent* aParent, ErrorResult& aRv); + nsresult + GetTargetPath(nsAString& aPath) const override; + protected: virtual FileSystemResponseValue GetSuccessRequestResult(ErrorResult& aRv) const override; diff --git a/dom/filesystem/GetFilesTask.cpp b/dom/filesystem/GetFilesTask.cpp index 02f0d538e260..cf3c12cef853 100644 --- a/dom/filesystem/GetFilesTask.cpp +++ b/dom/filesystem/GetFilesTask.cpp @@ -253,5 +253,11 @@ GetFilesTaskParent::IOWork() return NS_OK; } +nsresult +GetFilesTaskParent::GetTargetPath(nsAString& aPath) const +{ + return mTargetPath->GetPath(aPath); +} + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/GetFilesTask.h b/dom/filesystem/GetFilesTask.h index 348b0c552c84..be08a5d189c6 100644 --- a/dom/filesystem/GetFilesTask.h +++ b/dom/filesystem/GetFilesTask.h @@ -71,6 +71,9 @@ public: FileSystemRequestParent* aParent, ErrorResult& aRv); + nsresult + GetTargetPath(nsAString& aPath) const override; + private: GetFilesTaskParent(FileSystemBase* aFileSystem, const FileSystemGetFilesParams& aParam, diff --git a/dom/filesystem/compat/tests/test_basic.html b/dom/filesystem/compat/tests/test_basic.html index 1dff09c5d481..0ce6e6926ef9 100644 --- a/dom/filesystem/compat/tests/test_basic.html +++ b/dom/filesystem/compat/tests/test_basic.html @@ -16,6 +16,7 @@ var script; function setup_tests() { SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true], + ["dom.filesystem.pathcheck.disabled", true], ["dom.webkitBlink.filesystem.enabled", true]]}, next); } diff --git a/dom/filesystem/compat/tests/test_formSubmission.html b/dom/filesystem/compat/tests/test_formSubmission.html index 57a37d190f69..c920dcce1460 100644 --- a/dom/filesystem/compat/tests/test_formSubmission.html +++ b/dom/filesystem/compat/tests/test_formSubmission.html @@ -32,6 +32,7 @@ function setup_tests() { SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true], ["dom.webkitBlink.dirPicker.enabled", true], + ["dom.filesystem.pathcheck.disabled", true], ["dom.webkitBlink.filesystem.enabled", true]]}, next); } diff --git a/dom/filesystem/compat/tests/test_no_dnd.html b/dom/filesystem/compat/tests/test_no_dnd.html index 5554d442b012..4ddaaf33012d 100644 --- a/dom/filesystem/compat/tests/test_no_dnd.html +++ b/dom/filesystem/compat/tests/test_no_dnd.html @@ -16,6 +16,7 @@ var entries; function setup_tests() { SpecialPowers.pushPrefEnv({"set": [["dom.webkitBlink.dirPicker.enabled", true], + ["dom.filesystem.pathcheck.disabled", true], ["dom.webkitBlink.filesystem.enabled", true]]}, next); } diff --git a/dom/filesystem/moz.build b/dom/filesystem/moz.build index fa215ba9693b..bf32c9c56e58 100644 --- a/dom/filesystem/moz.build +++ b/dom/filesystem/moz.build @@ -15,6 +15,7 @@ EXPORTS.mozilla.dom += [ 'Directory.h', 'FileSystemBase.h', 'FileSystemRequestParent.h', + 'FileSystemSecurity.h', 'FileSystemTaskBase.h', 'FileSystemUtils.h', 'GetFilesHelper.h', @@ -25,6 +26,7 @@ UNIFIED_SOURCES += [ 'Directory.cpp', 'FileSystemBase.cpp', 'FileSystemRequestParent.cpp', + 'FileSystemSecurity.cpp', 'FileSystemTaskBase.cpp', 'FileSystemUtils.cpp', 'GetDirectoryListingTask.cpp', diff --git a/dom/filesystem/tests/filesystem_commons.js b/dom/filesystem/tests/filesystem_commons.js index b9ebc2d7334f..4f7234121e74 100644 --- a/dom/filesystem/tests/filesystem_commons.js +++ b/dom/filesystem/tests/filesystem_commons.js @@ -11,6 +11,7 @@ function createRelativePath(parentDir, dirOrFile) { function setup_tests(aNext) { SimpleTest.requestLongerTimeout(2); SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true], + ["dom.filesystem.pathcheck.disabled", true], ["dom.webkitBlink.dirPicker.enabled", true]]}, aNext); } diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index a9a9d74db63c..dd4f94618e36 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -41,6 +41,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileCreatorHelper.h" +#include "mozilla/dom/FileSystemSecurity.h" #include "mozilla/dom/ExternalHelperAppParent.h" #include "mozilla/dom/GetFilesHelper.h" #include "mozilla/dom/GeolocationBinding.h" @@ -1680,6 +1681,11 @@ ContentParent::ActorDestroy(ActorDestroyReason why) mHangMonitorActor = nullptr; } + RefPtr fss = FileSystemSecurity::Get(); + if (fss) { + fss->Forget(ChildID()); + } + if (why == NormalShutdown && !mCalledClose) { // If we shut down normally but haven't called Close, assume somebody // else called Close on us. In that case, we still need to call diff --git a/dom/ipc/FilePickerParent.cpp b/dom/ipc/FilePickerParent.cpp index 7d9346e7bbe4..f37e3695ddf5 100644 --- a/dom/ipc/FilePickerParent.cpp +++ b/dom/ipc/FilePickerParent.cpp @@ -13,6 +13,7 @@ #include "nsISimpleEnumerator.h" #include "mozilla/Unused.h" #include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/FileSystemSecurity.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/TabParent.h" @@ -145,6 +146,8 @@ FilePickerParent::IORunnable::Destroy() void FilePickerParent::SendFilesOrDirectories(const nsTArray& aData) { + nsIContentParent* parent = TabParent::GetFrom(Manager())->Manager(); + if (mMode == nsIFilePicker::modeGetFolder) { MOZ_ASSERT(aData.Length() <= 1); if (aData.IsEmpty()) { @@ -154,13 +157,18 @@ FilePickerParent::SendFilesOrDirectories(const nsTArray& aData MOZ_ASSERT(aData[0].mType == BlobImplOrString::eDirectoryPath); + // Let's inform the security singleton about the given access of this tab on + // this directory path. + RefPtr fss = FileSystemSecurity::GetOrCreate(); + fss->GrantAccessToContentProcess(parent->ChildID(), + aData[0].mDirectoryPath); + InputDirectory input; input.directoryPath() = aData[0].mDirectoryPath; Unused << Send__delete__(this, input, mResult); return; } - nsIContentParent* parent = TabParent::GetFrom(Manager())->Manager(); InfallibleTArray blobs; for (unsigned i = 0; i < aData.Length(); i++) { diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp index d2ddce4d93f6..0532ac43951e 100644 --- a/ipc/glue/BackgroundParentImpl.cpp +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -787,11 +787,18 @@ BackgroundParentImpl::AllocPFileSystemRequestParent( return nullptr; } - result->Start(); - return result.forget().take(); } +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPFileSystemRequestConstructor( + PFileSystemRequestParent* aActor, + const FileSystemParams& params) +{ + static_cast(aActor)->Start(); + return IPC_OK(); +} + bool BackgroundParentImpl::DeallocPFileSystemRequestParent( PFileSystemRequestParent* aDoomed) diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h index adce87536bc4..eeda791c879c 100644 --- a/ipc/glue/BackgroundParentImpl.h +++ b/ipc/glue/BackgroundParentImpl.h @@ -193,6 +193,10 @@ protected: virtual PFileSystemRequestParent* AllocPFileSystemRequestParent(const FileSystemParams&) override; + virtual mozilla::ipc::IPCResult + RecvPFileSystemRequestConstructor(PFileSystemRequestParent* actor, + const FileSystemParams& params) override; + virtual bool DeallocPFileSystemRequestParent(PFileSystemRequestParent*) override;