Bug 1872179 - Part 3: File picker file uploads consult content analysis r=rkraesig,win-reviewers

File picker upload operations check with any connected content analysis
tool before giving access to the web page.

Differential Revision: https://phabricator.services.mozilla.com/D198456
This commit is contained in:
Greg Stoll 2024-01-22 13:56:07 +00:00
parent a2c4862570
commit 0e786b6070
10 changed files with 204 additions and 22 deletions

View File

@ -818,7 +818,8 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
mode = nsIFilePicker::modeOpen;
}
nsresult rv = filePicker->Init(win, title, mode);
nsresult rv =
filePicker->Init(win, title, mode, OwnerDoc()->GetBrowsingContext());
NS_ENSURE_SUCCESS(rv, rv);
if (!okButtonLabel.IsEmpty()) {

View File

@ -11,11 +11,12 @@
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FileSystemSecurity.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FileSystemSecurity.h"
#include "mozilla/dom/IPCBlobUtils.h"
using mozilla::Unused;
@ -223,7 +224,9 @@ bool FilePickerParent::CreateFilePicker() {
return false;
}
Element* element = BrowserParent::GetFrom(Manager())->GetOwnerElement();
auto* browserParent = BrowserParent::GetFrom(Manager());
auto* browsingContext = browserParent->GetBrowsingContext();
Element* element = browserParent->GetOwnerElement();
if (!element) {
return false;
}
@ -233,7 +236,8 @@ bool FilePickerParent::CreateFilePicker() {
return false;
}
return NS_SUCCEEDED(mFilePicker->Init(window, mTitle, mMode));
return NS_SUCCEEDED(
mFilePicker->Init(window, mTitle, mMode, browsingContext));
}
mozilla::ipc::IPCResult FilePickerParent::RecvOpen(

View File

@ -244,7 +244,7 @@ nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath,
PRInt32 bytesRead = PR_Read(fd, buffer.get(), kBufferSize);
while (bytesRead != 0) {
if (bytesRead == -1) {
return NS_ERROR_DOM_FILE_NOT_READABLE_ERR;
return NS_ErrorAccordingToNSPR();
}
digest.Update(mozilla::Span<const uint8_t>(buffer.get(), bytesRead));
bytesRead = PR_Read(fd, buffer.get(), kBufferSize);

View File

@ -153,9 +153,10 @@ nsBaseFilePicker::nsBaseFilePicker()
nsBaseFilePicker::~nsBaseFilePicker() = default;
NS_IMETHODIMP nsBaseFilePicker::Init(mozIDOMWindowProxy* aParent,
const nsAString& aTitle,
nsIFilePicker::Mode aMode) {
NS_IMETHODIMP nsBaseFilePicker::Init(
mozIDOMWindowProxy* aParent, const nsAString& aTitle,
nsIFilePicker::Mode aMode,
mozilla::dom::BrowsingContext* aBrowsingContext) {
MOZ_ASSERT(aParent,
"Null parent passed to filepicker, no file "
"picker for you!");
@ -165,6 +166,7 @@ NS_IMETHODIMP nsBaseFilePicker::Init(mozIDOMWindowProxy* aParent,
nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(mParent);
NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
mBrowsingContext = aBrowsingContext;
mMode = aMode;
InitNative(widget, aTitle);

View File

@ -7,6 +7,7 @@
#ifndef nsBaseFilePicker_h__
#define nsBaseFilePicker_h__
#include "mozilla/dom/BrowsingContext.h"
#include "nsISupports.h"
#include "nsIFilePicker.h"
#include "nsISimpleEnumerator.h"
@ -28,7 +29,8 @@ class nsBaseFilePicker : public nsIFilePicker {
virtual ~nsBaseFilePicker();
NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
nsIFilePicker::Mode aMode) override;
nsIFilePicker::Mode aMode,
mozilla::dom::BrowsingContext* aBrowsingContext) override;
NS_IMETHOD IsModeSupported(nsIFilePicker::Mode aMode, JSContext* aCx,
mozilla::dom::Promise** aPromise) override;
#ifndef XP_WIN
@ -68,6 +70,9 @@ class nsBaseFilePicker : public nsIFilePicker {
nsString mDisplaySpecialDirectory;
nsCOMPtr<nsPIDOMWindowOuter> mParent;
// The BrowsingContext from which the file picker is being opened.
// Used for content analysis.
RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
nsIFilePicker::Mode mMode;
nsString mOkButtonLabel;
nsTArray<nsString> mRawFilters;

View File

@ -12,6 +12,7 @@
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/IPCBlobUtils.h"
using namespace mozilla::dom;
@ -25,7 +26,8 @@ nsFilePickerProxy::~nsFilePickerProxy() = default;
NS_IMETHODIMP
nsFilePickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
nsIFilePicker::Mode aMode) {
nsIFilePicker::Mode aMode,
BrowsingContext* aBrowsingContext) {
BrowserChild* browserChild = BrowserChild::GetFrom(aParent);
if (!browserChild) {
return NS_ERROR_FAILURE;

View File

@ -12,6 +12,7 @@
#include "nsTArray.h"
#include "nsCOMArray.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/PFilePickerChild.h"
#include "mozilla/dom/UnionTypes.h"
@ -34,7 +35,8 @@ class nsFilePickerProxy : public nsBaseFilePicker,
// nsIFilePicker (less what's in nsBaseFilePicker)
NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
nsIFilePicker::Mode aMode) override;
nsIFilePicker::Mode aMode,
mozilla::dom::BrowsingContext* aBrowsingContext) override;
NS_IMETHOD AppendFilter(const nsAString& aTitle,
const nsAString& aFilter) override;
NS_IMETHOD GetCapture(nsIFilePicker::CaptureTarget* aCapture) override;

View File

@ -10,6 +10,7 @@ interface nsIFile;
interface nsIURI;
interface mozIDOMWindowProxy;
interface nsISimpleEnumerator;
webidl BrowsingContext;
// Declared in this file, below.
interface nsIFilePickerShownCallback;
@ -69,9 +70,15 @@ interface nsIFilePicker : nsISupports
* on this parent. parent must be non-null.
* @param title The title for the file widget
* @param mode load, save, or get folder
*
* @param browsingContext [optional]
* The context in which the file picker is being shown. This is
* used for content analysis and can be omitted if chrome is
* showing the file picker.
*/
void init(in mozIDOMWindowProxy parent, in AString title, in nsIFilePicker_Mode mode);
void init(in mozIDOMWindowProxy parent,
in AString title,
in nsIFilePicker_Mode mode,
[optional] in BrowsingContext browsingContext);
/**
* Returns a Promise that resolves to true if the passed nsIFilePicker mode

View File

@ -14,8 +14,11 @@
#include <winuser.h>
#include <utility>
#include "ContentAnalysis.h"
#include "mozilla/Assertions.h"
#include "mozilla/BackgroundHangMonitor.h"
#include "mozilla/Components.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/Logging.h"
#include "mozilla/ipc/UtilityProcessManager.h"
#include "mozilla/ProfilerLabels.h"
@ -24,6 +27,7 @@
#include "mozilla/WindowsVersion.h"
#include "nsCRT.h"
#include "nsEnumeratorUtils.h"
#include "nsIContentAnalysis.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsPrintfCString.h"
@ -84,9 +88,10 @@ nsFilePicker::nsFilePicker() : mSelectedType(1) {}
NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
NS_IMETHODIMP nsFilePicker::Init(mozIDOMWindowProxy* aParent,
const nsAString& aTitle,
nsIFilePicker::Mode aMode) {
NS_IMETHODIMP nsFilePicker::Init(
mozIDOMWindowProxy* aParent, const nsAString& aTitle,
nsIFilePicker::Mode aMode,
mozilla::dom::BrowsingContext* aBrowsingContext) {
// Don't attempt to open a real file-picker in headless mode.
if (gfxPlatform::IsHeadless()) {
return nsresult::NS_ERROR_NOT_AVAILABLE;
@ -96,7 +101,7 @@ NS_IMETHODIMP nsFilePicker::Init(mozIDOMWindowProxy* aParent,
nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
mLoadContext = do_QueryInterface(docShell);
return nsBaseFilePicker::Init(aParent, aTitle, aMode);
return nsBaseFilePicker::Init(aParent, aTitle, aMode, aBrowsingContext);
}
namespace mozilla::detail {
@ -191,10 +196,11 @@ static auto ShowRemote(HWND parent, ActionType&& action)
// fd_async
//
// Wrapper-namespace for the AsyncExecute() function.
// Wrapper-namespace for the AsyncExecute() and AsyncAll() functions.
namespace fd_async {
// Implementation details of, specifically, the AsyncExecute() function.
// Implementation details of, specifically, the AsyncExecute() and AsyncAll()
// functions.
namespace details {
// Helper for generically copying ordinary types and nsTArray (which lacks a
// copy constructor) in the same breath.
@ -234,6 +240,49 @@ static Strategy GetStrategy() {
}
};
template <typename T>
class AsyncAllIterator final {
public:
NS_INLINE_DECL_REFCOUNTING(AsyncAllIterator)
AsyncAllIterator(
nsTArray<T> aItems,
std::function<RefPtr<mozilla::GenericPromise>(const T& item)> aPredicate,
RefPtr<mozilla::GenericPromise::Private> aPromise)
: mItems(std::move(aItems)),
mNextIndex(0),
mPredicate(std::move(aPredicate)),
mPromise(std::move(aPromise)) {}
void StartIterating() { ContinueIterating(); }
private:
~AsyncAllIterator() = default;
void ContinueIterating() {
if (mNextIndex >= mItems.Length()) {
mPromise->Resolve(true, __func__);
return;
}
mPredicate(mItems.ElementAt(mNextIndex))
->Then(
mozilla::GetMainThreadSerialEventTarget(), __func__,
[self = RefPtr{this}](bool aResult) {
if (!aResult) {
self->mPromise->Resolve(false, __func__);
return;
}
++self->mNextIndex;
self->ContinueIterating();
},
[self = RefPtr{this}](nsresult aError) {
self->mPromise->Reject(aError, __func__);
});
}
nsTArray<T> mItems;
uint32_t mNextIndex;
std::function<RefPtr<mozilla::GenericPromise>(const T& item)> mPredicate;
RefPtr<mozilla::GenericPromise::Private> mPromise;
};
namespace telemetry {
static uint32_t Delta(uint64_t tb, uint64_t ta) {
// FILETIMEs are 100ns intervals; we reduce that to 1ms.
@ -348,8 +397,24 @@ static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args)
});
});
}
// Asynchronously invokes `aPredicate` on each member of `aItems`.
// Yields `false` (and stops immediately) if any invocation of
// `predicate` yielded `false`; otherwise yields `true`.
template <typename T>
static RefPtr<mozilla::GenericPromise> AsyncAll(
nsTArray<T> aItems,
std::function<RefPtr<mozilla::GenericPromise>(const T& item)> aPredicate) {
auto promise =
mozilla::MakeRefPtr<mozilla::GenericPromise::Private>(__func__);
auto iterator = mozilla::MakeRefPtr<details::AsyncAllIterator<T>>(
std::move(aItems), aPredicate, promise);
iterator->StartIterating();
return promise;
}
} // namespace fd_async
using fd_async::AsyncAll;
using fd_async::AsyncExecute;
} // namespace mozilla::detail
@ -614,6 +679,77 @@ void nsFilePicker::ClearFiles() {
mFiles.Clear();
}
RefPtr<mozilla::GenericPromise> nsFilePicker::CheckContentAnalysisService() {
nsresult rv;
nsCOMPtr<nsIContentAnalysis> contentAnalysis =
mozilla::components::nsIContentAnalysis::Service(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return mozilla::GenericPromise::CreateAndReject(rv, __func__);
}
bool contentAnalysisIsActive = false;
rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
if (NS_WARN_IF(NS_FAILED(rv))) {
return mozilla::GenericPromise::CreateAndReject(rv, __func__);
}
if (!contentAnalysisIsActive) {
return mozilla::GenericPromise::CreateAndResolve(true, __func__);
}
nsCOMArray<nsIFile> files;
if (!mUnicodeFile.IsEmpty()) {
nsCOMPtr<nsIFile> file;
rv = GetFile(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return mozilla::GenericPromise::CreateAndReject(rv, __func__);
}
files.AppendElement(file);
} else {
files.AppendElements(mFiles);
}
nsTArray<mozilla::PathString> paths(files.Length());
std::transform(files.begin(), files.end(), MakeBackInserter(paths),
[](auto* entry) { return entry->NativePath(); });
RefPtr<nsIURI> uri = mBrowsingContext->Canonical()->GetCurrentURI();
nsCString uriCString;
rv = uri->GetSpec(uriCString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return mozilla::GenericPromise::CreateAndReject(rv, __func__);
}
nsString uriString = NS_ConvertUTF8toUTF16(uriCString);
auto promise = mozilla::detail::AsyncAll<mozilla::PathString>(
std::move(paths),
[self = RefPtr{this}, contentAnalysis = std::move(contentAnalysis),
uriString = std::move(uriString)](const mozilla::PathString& aItem) {
nsCString emptyDigestString;
auto* windowGlobal =
self->mBrowsingContext->Canonical()->GetCurrentWindowGlobal();
nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest(
new mozilla::contentanalysis::ContentAnalysisRequest(
nsIContentAnalysisRequest::AnalysisType::eFileAttached, aItem,
true, std::move(emptyDigestString), uriString,
nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
windowGlobal));
auto promise =
mozilla::MakeRefPtr<mozilla::GenericPromise::Private>(__func__);
auto contentAnalysisCallback = mozilla::MakeRefPtr<
mozilla::contentanalysis::ContentAnalysisCallback>(
[promise](nsIContentAnalysisResponse* aResponse) {
promise->Resolve(aResponse->GetShouldAllowContent(), __func__);
},
[promise](nsresult aError) { promise->Reject(aError, __func__); });
nsresult rv = contentAnalysis->AnalyzeContentRequestCallback(
contentAnalysisRequest, true, contentAnalysisCallback);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->Reject(rv, __func__);
}
return promise;
});
return promise;
}
///////////////////////////////////////////////////////////////////////////////
// nsIFilePicker impl.
@ -666,6 +802,25 @@ nsresult nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
}
}
if (self->mBrowsingContext && !self->mBrowsingContext->IsChrome() &&
self->mMode != modeSave && retValue != ResultCode::returnCancel) {
self->CheckContentAnalysisService()->Then(
mozilla::GetMainThreadSerialEventTarget(), __func__,
[retValue, callback, self = RefPtr{self}](bool aAllowContent) {
if (aAllowContent) {
callback->Done(retValue);
} else {
self->ClearFiles();
callback->Done(ResultCode::returnCancel);
}
},
[callback, self = RefPtr{self}](nsresult aError) {
self->ClearFiles();
callback->Done(ResultCode::returnCancel);
});
return;
}
callback->Done(retValue);
},
[callback = RefPtr(aCallback)](HRESULT err) {

View File

@ -10,6 +10,8 @@
#include <windows.h>
#include "mozilla/MozPromise.h"
#include "mozilla/dom/BrowsingContext.h"
#include "nsIContentAnalysis.h"
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
#include "nsCOMArray.h"
@ -63,7 +65,8 @@ class nsFilePicker final : public nsBaseWinFilePicker {
nsFilePicker();
NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
nsIFilePicker::Mode aMode) override;
nsIFilePicker::Mode aMode,
mozilla::dom::BrowsingContext* aBrowsingContext) override;
NS_DECL_ISUPPORTS
@ -103,6 +106,7 @@ class nsFilePicker final : public nsBaseWinFilePicker {
HWND aParent, nsTArray<Command> const& commands);
void ClearFiles();
RefPtr<mozilla::GenericPromise> CheckContentAnalysisService();
protected:
void RememberLastUsedDirectory();