Bug 1344415 - Introduce a security check about which files can be opened by a content process, r=ehsan

Entries API gives access to the filesystem. With this patch we allow that only
to patches, previously shared by a FilePicker or a DataTransfer event.
This commit is contained in:
Andrea Marchesini 2017-03-16 08:53:49 +01:00
Родитель 2a318e8c71
Коммит a628be7b36
22 изменённых файлов: 334 добавлений и 21 удалений

Просмотреть файл

@ -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<FileSystemSecurity> fss = FileSystemSecurity::GetOrCreate();
fss->GrantAccessToContentProcess(aParent->ChildID(), path);
}
}
blobImpl = new FileBlobImpl(file);
IgnoredErrorResult rv;

Просмотреть файл

@ -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<ContentParent> 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<FileSystemSecurity> 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<ContentParent> mContentParent;
RefPtr<FileSystemRequestParent> mActor;
RefPtr<FileSystemTaskParentBase> mTask;
const nsString mPath;
nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
};
} // anonymous
void
FileSystemRequestParent::Start()
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mDestroyed);
MOZ_ASSERT(mFileSystem);
MOZ_ASSERT(mTask);
nsAutoString path;
if (NS_WARN_IF(NS_FAILED(mTask->GetTargetPath(path)))) {
Unused << Send__delete__(this, FileSystemErrorResponse(NS_ERROR_DOM_SECURITY_ERR));
return;
}
RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(Manager());
// If the ContentParent is null we are dealing with a same-process actor.
if (!parent) {
mTask->Start();
return;
}
RefPtr<Runnable> runnable =
new CheckPermissionRunnable(parent.forget(), this, mTask, path);
NS_DispatchToMainThread(runnable);
}
void

Просмотреть файл

@ -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<FileSystemSecurity> gFileSystemSecurity;
} // anonymous
/* static */ already_AddRefed<FileSystemSecurity>
FileSystemSecurity::Get()
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsInMainProcess();
RefPtr<FileSystemSecurity> service = gFileSystemSecurity.get();
return service.forget();
}
/* static */ already_AddRefed<FileSystemSecurity>
FileSystemSecurity::GetOrCreate()
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsInMainProcess();
if (!gFileSystemSecurity) {
gFileSystemSecurity = new FileSystemSecurity();
ClearOnShutdown(&gFileSystemSecurity);
}
RefPtr<FileSystemSecurity> 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<nsString>* paths;
if (!mPaths.Get(aId, &paths)) {
paths = new nsTArray<nsString>();
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<nsString>* 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

Просмотреть файл

@ -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<FileSystemSecurity>
Get();
static already_AddRefed<FileSystemSecurity>
GetOrCreate();
void
GrantAccessToContentProcess(ContentParentId aId,
const nsAString& aDirectoryPath);
void
Forget(ContentParentId aId);
bool
ContentProcessHasAccessTo(ContentParentId aId, const nsAString& aPath);
private:
FileSystemSecurity();
~FileSystemSecurity();
nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mPaths;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_FileSystemSecurity_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.

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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

Просмотреть файл

@ -380,5 +380,11 @@ GetDirectoryListingTaskParent::IOWork()
return NS_OK;
}
nsresult
GetDirectoryListingTaskParent::GetTargetPath(nsAString& aPath) const
{
return mTargetPath->GetPath(aPath);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -69,6 +69,9 @@ public:
FileSystemRequestParent* aParent,
ErrorResult& aRv);
nsresult
GetTargetPath(nsAString& aPath) const override;
private:
GetDirectoryListingTaskParent(FileSystemBase* aFileSystem,
const FileSystemGetDirectoryListingParams& aParam,

Просмотреть файл

@ -270,5 +270,11 @@ GetFileOrDirectoryTaskParent::IOWork()
return NS_OK;
}
nsresult
GetFileOrDirectoryTaskParent::GetTargetPath(nsAString& aPath) const
{
return mTargetPath->GetPath(aPath);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -62,6 +62,9 @@ public:
FileSystemRequestParent* aParent,
ErrorResult& aRv);
nsresult
GetTargetPath(nsAString& aPath) const override;
protected:
virtual FileSystemResponseValue
GetSuccessRequestResult(ErrorResult& aRv) const override;

Просмотреть файл

@ -253,5 +253,11 @@ GetFilesTaskParent::IOWork()
return NS_OK;
}
nsresult
GetFilesTaskParent::GetTargetPath(nsAString& aPath) const
{
return mTargetPath->GetPath(aPath);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -71,6 +71,9 @@ public:
FileSystemRequestParent* aParent,
ErrorResult& aRv);
nsresult
GetTargetPath(nsAString& aPath) const override;
private:
GetFilesTaskParent(FileSystemBase* aFileSystem,
const FileSystemGetFilesParams& aParam,

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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',

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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<FileSystemSecurity> 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

Просмотреть файл

@ -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<BlobImplOrString>& 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<BlobImplOrString>& 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<FileSystemSecurity> 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<PBlobParent*> blobs;
for (unsigned i = 0; i < aData.Length(); i++) {

Просмотреть файл

@ -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<FileSystemRequestParent*>(aActor)->Start();
return IPC_OK();
}
bool
BackgroundParentImpl::DeallocPFileSystemRequestParent(
PFileSystemRequestParent* aDoomed)

Просмотреть файл

@ -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;