diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/contentanalysis/ContentAnalysis.cpp | 334 |
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) { |