summaryrefslogtreecommitdiffstats
path: root/toolkit/components/contentanalysis/ContentAnalysis.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/contentanalysis/ContentAnalysis.cpp')
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.cpp334
1 files changed, 311 insertions, 23 deletions
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.cpp b/toolkit/components/contentanalysis/ContentAnalysis.cpp
index 2977072984..4f4c53a324 100644
--- a/toolkit/components/contentanalysis/ContentAnalysis.cpp
+++ b/toolkit/components/contentanalysis/ContentAnalysis.cpp
@@ -17,9 +17,11 @@
#include "mozilla/Logging.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_browser.h"
#include "nsAppRunner.h"
+#include "nsBaseClipboard.h"
#include "nsComponentManagerUtils.h"
#include "nsIClassInfoImpl.h"
#include "nsIFile.h"
@@ -28,6 +30,8 @@
#include "nsIOutputStream.h"
#include "nsIPrintSettings.h"
#include "nsIStorageStream.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransferable.h"
#include "ScopedNSSTypes.h"
#include "xpcpublic.h"
@@ -61,7 +65,6 @@ namespace {
const char* kIsDLPEnabledPref = "browser.contentanalysis.enabled";
const char* kIsPerUserPref = "browser.contentanalysis.is_per_user";
const char* kPipePathNamePref = "browser.contentanalysis.pipe_path_name";
-const char* kDefaultAllowPref = "browser.contentanalysis.default_allow";
const char* kClientSignature = "browser.contentanalysis.client_signature";
const char* kAllowUrlPref = "browser.contentanalysis.allow_url_regex_list";
const char* kDenyUrlPref = "browser.contentanalysis.deny_url_regex_list";
@@ -793,6 +796,17 @@ static bool ShouldAllowAction(
aResponseCode == nsIContentAnalysisResponse::Action::eReportOnly ||
aResponseCode == nsIContentAnalysisResponse::Action::eWarn;
}
+
+static DefaultResult GetDefaultResultFromPref() {
+ uint32_t value = StaticPrefs::browser_contentanalysis_default_result();
+ if (value > static_cast<uint32_t>(DefaultResult::eLastValue)) {
+ LOGE(
+ "Invalid value for browser.contentanalysis.default_result pref "
+ "value");
+ return DefaultResult::eBlock;
+ }
+ return static_cast<DefaultResult>(value);
+}
} // namespace
NS_IMETHODIMP ContentAnalysisResponse::GetShouldAllowContent(
@@ -805,7 +819,7 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
bool* aShouldAllowContent) {
if (mValue.is<NoContentAnalysisResult>()) {
NoContentAnalysisResult result = mValue.as<NoContentAnalysisResult>();
- if (Preferences::GetBool(kDefaultAllowPref)) {
+ if (GetDefaultResultFromPref() == DefaultResult::eAllow) {
*aShouldAllowContent =
result != NoContentAnalysisResult::DENY_DUE_TO_CANCELED;
} else {
@@ -816,6 +830,7 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE ||
result == NoContentAnalysisResult::
ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS ||
+ result == NoContentAnalysisResult::ALLOW_DUE_TO_SAME_TAB_SOURCE ||
result == NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA;
}
} else {
@@ -1062,7 +1077,7 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
nsresult aResult) {
return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
"ContentAnalysis::CancelWithError",
- [aResult, aRequestToken = std::move(aRequestToken)] {
+ [aResult, aRequestToken = std::move(aRequestToken)]() mutable {
RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
if (!owner) {
// May be shutting down
@@ -1071,12 +1086,24 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
owner->SetLastResult(aResult);
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
- bool allow = Preferences::GetBool(kDefaultAllowPref);
+ DefaultResult defaultResponse = GetDefaultResultFromPref();
+ nsIContentAnalysisResponse::Action action;
+ switch (defaultResponse) {
+ case DefaultResult::eAllow:
+ action = nsIContentAnalysisResponse::Action::eAllow;
+ break;
+ case DefaultResult::eWarn:
+ action = nsIContentAnalysisResponse::Action::eWarn;
+ break;
+ case DefaultResult::eBlock:
+ action = nsIContentAnalysisResponse::Action::eCanceled;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ action = nsIContentAnalysisResponse::Action::eCanceled;
+ }
RefPtr<ContentAnalysisResponse> response =
- ContentAnalysisResponse::FromAction(
- allow ? nsIContentAnalysisResponse::Action::eAllow
- : nsIContentAnalysisResponse::Action::eCanceled,
- aRequestToken);
+ ContentAnalysisResponse::FromAction(action, aRequestToken);
response->SetOwner(owner);
nsIContentAnalysisResponse::CancelError cancelError;
switch (aResult) {
@@ -1092,20 +1119,29 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
break;
}
response->SetCancelError(cancelError);
- obsServ->NotifyObservers(response, "dlp-response", nullptr);
- nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder;
+ Maybe<CallbackData> maybeCallbackData;
{
auto lock = owner->mCallbackMap.Lock();
- auto callbackData = lock->Extract(aRequestToken);
- if (callbackData.isSome()) {
- callbackHolder = callbackData->TakeCallbackHolder();
+ maybeCallbackData = lock->Extract(aRequestToken);
+ if (maybeCallbackData.isNothing()) {
+ LOGD("Content analysis did not find callback for token %s",
+ aRequestToken.get());
+ return;
}
}
+ if (action == nsIContentAnalysisResponse::Action::eWarn) {
+ owner->SendWarnResponse(std::move(aRequestToken),
+ std::move(*maybeCallbackData), response);
+ return;
+ }
+ nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
+ maybeCallbackData->TakeCallbackHolder();
+ obsServ->NotifyObservers(response, "dlp-response", nullptr);
if (callbackHolder) {
- if (allow) {
- callbackHolder->ContentResult(response);
- } else {
+ if (action == nsIContentAnalysisResponse::Action::eCanceled) {
callbackHolder->Error(aResult);
+ } else {
+ callbackHolder->ContentResult(response);
}
}
}));
@@ -1184,6 +1220,22 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
ConvertToProtobuf(aRequest, GetUserActionId(), aRequestCount, &pbRequest);
NS_ENSURE_SUCCESS(rv, rv);
LogRequest(&pbRequest);
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ // Avoid serializing the string here if no one is observing this message
+ if (obsServ->HasObservers("dlp-request-sent-raw")) {
+ std::string requestString = pbRequest.SerializeAsString();
+ nsTArray<char16_t> requestArray;
+ requestArray.SetLength(requestString.size() + 1);
+ for (size_t i = 0; i < requestString.size(); ++i) {
+ // Since NotifyObservers() expects a null-terminated string,
+ // make sure none of these values are 0.
+ requestArray[i] = requestString[i] + 0xFF00;
+ }
+ requestArray[requestString.size()] = 0;
+ obsServ->NotifyObservers(this, "dlp-request-sent-raw",
+ requestArray.Elements());
+ }
mCaClientPromise->Then(
GetCurrentSerialEventTarget(), __func__,
@@ -1289,6 +1341,20 @@ void ContentAnalysis::DoAnalyzeRequest(
}));
}
+void ContentAnalysis::SendWarnResponse(
+ nsCString&& aResponseRequestToken, CallbackData aCallbackData,
+ RefPtr<ContentAnalysisResponse>& aResponse) {
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ {
+ auto warnResponseDataMap = mWarnResponseDataMap.Lock();
+ warnResponseDataMap->InsertOrUpdate(
+ aResponseRequestToken,
+ WarnResponseData(std::move(aCallbackData), aResponse));
+ }
+ obsServ->NotifyObservers(aResponse, "dlp-response", nullptr);
+}
+
void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) {
MOZ_ASSERT(NS_IsMainThread());
nsCString responseRequestToken;
@@ -1336,13 +1402,8 @@ void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) {
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
if (action == nsIContentAnalysisResponse::Action::eWarn) {
- {
- auto warnResponseDataMap = mWarnResponseDataMap.Lock();
- warnResponseDataMap->InsertOrUpdate(
- responseRequestToken,
- WarnResponseData(std::move(*maybeCallbackData), response));
- }
- obsServ->NotifyObservers(response, "dlp-response", nullptr);
+ SendWarnResponse(std::move(responseRequestToken),
+ std::move(*maybeCallbackData), response);
return;
}
@@ -1696,6 +1757,233 @@ ContentAnalysis::PrintToPDFToDetermineIfPrintAllowed(
}
#endif
+NS_IMPL_ISUPPORTS(ContentAnalysis::SafeContentAnalysisResultCallback,
+ nsIContentAnalysisCallback);
+
+// - true means a content analysis request was fired
+// - false means there is no text data in the transferable
+// - NoContentAnalysisResult means there was an error
+using ClipboardContentAnalysisResult =
+ mozilla::Result<bool, mozilla::contentanalysis::NoContentAnalysisResult>;
+
+NS_IMETHODIMP ContentAnalysis::SafeContentAnalysisResultCallback::ContentResult(
+ nsIContentAnalysisResponse* aResponse) {
+ RefPtr<ContentAnalysisResult> result =
+ ContentAnalysisResult::FromContentAnalysisResponse(aResponse);
+ Callback(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ContentAnalysis::SafeContentAnalysisResultCallback::Error(
+ nsresult aError) {
+ Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
+ return NS_OK;
+}
+
+ClipboardContentAnalysisResult CheckClipboardContentAnalysisAsText(
+ uint64_t aInnerWindowId,
+ ContentAnalysis::SafeContentAnalysisResultCallback* aResolver,
+ nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
+ nsITransferable* aTextTrans, const char* aFlavor) {
+ nsCOMPtr<nsISupports> transferData;
+ if (NS_FAILED(
+ aTextTrans->GetTransferData(aFlavor, getter_AddRefs(transferData)))) {
+ return false;
+ }
+ nsCOMPtr<nsISupportsString> textData = do_QueryInterface(transferData);
+ if (MOZ_UNLIKELY(!textData)) {
+ return false;
+ }
+ nsString text;
+ if (NS_FAILED(textData->GetData(text))) {
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ if (text.IsEmpty()) {
+ // Content Analysis doesn't expect to analyze an empty string.
+ // Just approve it.
+ return true;
+ }
+ RefPtr<mozilla::dom::WindowGlobalParent> window =
+ mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
+ if (!window) {
+ // The window has gone away in the meantime
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
+ new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
+ std::move(text), false, EmptyCString(), aDocumentURI,
+ nsIContentAnalysisRequest::OperationType::eClipboard, window);
+ nsresult rv = aContentAnalysis->AnalyzeContentRequestCallback(
+ contentAnalysisRequest, /* aAutoAcknowledge */ true, aResolver);
+ if (NS_FAILED(rv)) {
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ return true;
+}
+
+ClipboardContentAnalysisResult CheckClipboardContentAnalysisAsFile(
+ uint64_t aInnerWindowId,
+ ContentAnalysis::SafeContentAnalysisResultCallback* aResolver,
+ nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
+ nsITransferable* aFileTrans) {
+ nsCOMPtr<nsISupports> transferData;
+ nsresult rv =
+ aFileTrans->GetTransferData(kFileMime, getter_AddRefs(transferData));
+ nsString filePath;
+ if (NS_SUCCEEDED(rv)) {
+ if (nsCOMPtr<nsIFile> file = do_QueryInterface(transferData)) {
+ rv = file->GetPath(filePath);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ }
+ if (NS_FAILED(rv) || filePath.IsEmpty()) {
+ return false;
+ }
+ RefPtr<mozilla::dom::WindowGlobalParent> window =
+ mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
+ if (!window) {
+ // The window has gone away in the meantime
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ // Let the content analysis code calculate the digest
+ nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
+ new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
+ std::move(filePath), true, EmptyCString(), aDocumentURI,
+ nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
+ window);
+ rv = aContentAnalysis->AnalyzeContentRequestCallback(
+ contentAnalysisRequest,
+ /* aAutoAcknowledge */ true, aResolver);
+ if (NS_FAILED(rv)) {
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ return true;
+}
+
+void ContentAnalysis::CheckClipboardContentAnalysis(
+ nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow,
+ nsITransferable* aTransferable, int32_t aClipboardType,
+ SafeContentAnalysisResultCallback* aResolver) {
+ using namespace mozilla::contentanalysis;
+
+ // Content analysis is only needed if an outside webpage has access to
+ // the data. So, skip content analysis if there is:
+ // - no associated window (for example, scripted clipboard read by system
+ // code)
+ // - the window is a chrome docshell
+ // - the window is being rendered in the parent process (for example,
+ // about:support and the like)
+ if (!aWindow || aWindow->GetBrowsingContext()->IsChrome() ||
+ aWindow->IsInProcess()) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::
+ ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS));
+ return;
+ }
+ nsCOMPtr<nsIContentAnalysis> contentAnalysis =
+ mozilla::components::nsIContentAnalysis::Service();
+ if (!contentAnalysis) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
+ return;
+ }
+
+ bool contentAnalysisIsActive;
+ nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
+ if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE));
+ return;
+ }
+
+ uint64_t innerWindowId = aWindow->InnerWindowId();
+ if (mozilla::StaticPrefs::
+ browser_contentanalysis_bypass_for_same_tab_operations()) {
+ mozilla::Maybe<uint64_t> cacheInnerWindowId =
+ aClipboard->GetClipboardCacheInnerWindowId(aClipboardType);
+ if (cacheInnerWindowId.isSome() && *cacheInnerWindowId == innerWindowId) {
+ // If the same page copied this data to the clipboard (and the above
+ // preference is set) we can skip content analysis and immediately allow
+ // this.
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::ALLOW_DUE_TO_SAME_TAB_SOURCE));
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIURI> currentURI = aWindow->Canonical()->GetDocumentURI();
+ nsTArray<nsCString> flavors;
+ rv = aTransferable->FlavorsTransferableCanExport(flavors);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
+ return;
+ }
+ bool keepChecking = true;
+ if (flavors.Contains(kFileMime)) {
+ auto fileResult = CheckClipboardContentAnalysisAsFile(
+ innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
+
+ if (fileResult.isErr()) {
+ aResolver->Callback(
+ ContentAnalysisResult::FromNoResult(fileResult.unwrapErr()));
+ return;
+ }
+ keepChecking = !fileResult.unwrap();
+ }
+ if (keepChecking) {
+ // Failed to get the clipboard data as a file, so try as text
+ auto textResult = CheckClipboardContentAnalysisAsText(
+ innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable,
+ kTextMime);
+ if (textResult.isErr()) {
+ aResolver->Callback(
+ ContentAnalysisResult::FromNoResult(textResult.unwrapErr()));
+ return;
+ }
+ keepChecking = !textResult.unwrap();
+ }
+ if (keepChecking) {
+ // Failed to get the clipboard data as a file or text, so try as html
+ auto htmlResult = CheckClipboardContentAnalysisAsText(
+ innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable,
+ kHTMLMime);
+ if (htmlResult.isErr()) {
+ aResolver->Callback(
+ ContentAnalysisResult::FromNoResult(htmlResult.unwrapErr()));
+ return;
+ }
+ if (!htmlResult.unwrap()) {
+ // Couldn't get file or text or html data from this
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA));
+ return;
+ }
+ }
+}
+
+bool ContentAnalysis::CheckClipboardContentAnalysisSync(
+ nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow,
+ const nsCOMPtr<nsITransferable>& trans, int32_t aClipboardType) {
+ bool requestDone = false;
+ RefPtr<nsIContentAnalysisResult> result;
+ auto callback = mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
+ [&requestDone, &result](RefPtr<nsIContentAnalysisResult>&& aResult) {
+ result = std::move(aResult);
+ requestDone = true;
+ });
+ CheckClipboardContentAnalysis(aClipboard, aWindow, trans, aClipboardType,
+ callback);
+ mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns,
+ [&requestDone]() -> bool { return requestDone; });
+ return result->GetShouldAllowContent();
+}
+
NS_IMETHODIMP
ContentAnalysisResponse::Acknowledge(
nsIContentAnalysisAcknowledgement* aAcknowledgement) {