Bug 1941725 - Make blocking of PDFs in sandboxed containers stricter. r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D245598
This commit is contained in:
Andreas Farre
2025-04-28 18:36:45 +00:00
parent a96dfaa896
commit 36441fce7d
2 changed files with 81 additions and 49 deletions

View File

@@ -283,14 +283,6 @@ class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
return nsDocumentOpenInfo::TryStreamConversion(aChannel); return nsDocumentOpenInfo::TryStreamConversion(aChannel);
} }
if (nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
loadInfo->GetSandboxFlags() &&
mContentType.LowerCaseEqualsLiteral(APPLICATION_PDF)) {
// Sandboxed iframes are just never allowed to display plugins. In the
// modern world, this just means "application/pdf".
return NS_ERROR_FAILURE;
}
nsresult rv; nsresult rv;
nsCOMPtr<nsIStreamConverterService> streamConvService; nsCOMPtr<nsIStreamConverterService> streamConvService;
nsAutoCString str; nsAutoCString str;

View File

@@ -6,6 +6,7 @@
#include "nsURILoader.h" #include "nsURILoader.h"
#include "nsComponentManagerUtils.h" #include "nsComponentManagerUtils.h"
#include "nsContentSecurityUtils.h"
#include "nsIURIContentListener.h" #include "nsIURIContentListener.h"
#include "nsIContentHandler.h" #include "nsIContentHandler.h"
#include "nsILoadGroup.h" #include "nsILoadGroup.h"
@@ -42,6 +43,7 @@
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/IntegerPrintfMacros.h" #include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "mozilla/Result.h"
#include "mozilla/Unused.h" #include "mozilla/Unused.h"
#include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_dom.h"
@@ -317,6 +319,62 @@ NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest* request,
return NS_OK; return NS_OK;
} }
static bool IsContentPDF(nsIChannel* aChannel, const nsACString& aContentType) {
bool isPDF = aContentType.LowerCaseEqualsASCII(APPLICATION_PDF);
if (!isPDF && (aContentType.LowerCaseEqualsASCII(APPLICATION_OCTET_STREAM) ||
aContentType.IsEmpty())) {
nsAutoString flname;
aChannel->GetContentDispositionFilename(flname);
isPDF = StringEndsWith(flname, u".pdf"_ns);
if (!isPDF) {
nsCOMPtr<nsIURI> uri;
aChannel->GetURI(getter_AddRefs(uri));
nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
if (url) {
nsAutoCString ext;
url->GetFileExtension(ext);
isPDF = ext.EqualsLiteral("pdf");
}
}
}
return isPDF;
}
static mozilla::Result<bool, nsresult> ShouldHandleExternally(
const nsACString& aMimeType) {
// For a PDF, check if the preference is set that forces attachments to be
// opened inline. If so, treat it as a non-attachment by clearing
// 'forceExternalHandling' again. This allows it open a PDF directly
// instead of downloading it first. It may still end up being handled by
// a helper app depending anyway on the later checks.
nsCOMPtr<nsIMIMEInfo> mimeInfo;
nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
if (!mimeSvc) {
return mozilla::Err(NS_ERROR_FAILURE);
}
mimeSvc->GetFromTypeAndExtension(aMimeType, EmptyCString(),
getter_AddRefs(mimeInfo));
if (mimeInfo) {
int32_t action = nsIMIMEInfo::saveToDisk;
mimeInfo->GetPreferredAction(&action);
bool alwaysAsk = true;
mimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
return alwaysAsk || action != nsIMIMEInfo::handleInternally;
}
return false;
}
static bool IsSandboxed(nsIChannel* aChannel) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
return loadInfo->GetSandboxFlags();
}
nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) { nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) {
LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this,
mContentType.get())); mContentType.get()));
@@ -367,56 +425,38 @@ nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) {
} }
LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no")); LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
LOG((" IsSandboxed: %s", IsSandboxed(aChannel) ? "yes" : "no"));
LOG((" IsContentPDF: %s",
IsContentPDF(aChannel, mContentType) ? "yes" : "no"));
if (forceExternalHandling && bool maybeForceInternalHandling =
mozilla::StaticPrefs::browser_download_open_pdf_attachments_inline()) { forceExternalHandling &&
// Check if this is a PDF which should be opened internally. We also handle mozilla::StaticPrefs::browser_download_open_pdf_attachments_inline();
// octet-streams that look like they might be PDFs based on their extension.
bool isPDF = mContentType.LowerCaseEqualsASCII(APPLICATION_PDF);
if (!isPDF &&
(mContentType.LowerCaseEqualsASCII(APPLICATION_OCTET_STREAM) ||
mContentType.IsEmpty())) {
nsAutoString flname;
aChannel->GetContentDispositionFilename(flname);
isPDF = StringEndsWith(flname, u".pdf"_ns);
if (!isPDF) {
nsCOMPtr<nsIURI> uri;
aChannel->GetURI(getter_AddRefs(uri));
nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
if (url) {
nsAutoCString ext;
url->GetFileExtension(ext);
isPDF = ext.EqualsLiteral("pdf");
}
}
}
// Check if this is a PDF which should be opened internally. We also handle
// octet-streams that look like they might be PDFs based on their extension.
if ((maybeForceInternalHandling || IsSandboxed(aChannel)) &&
IsContentPDF(aChannel, mContentType)) {
// For a PDF, check if the preference is set that forces attachments to be // For a PDF, check if the preference is set that forces attachments to be
// opened inline. If so, treat it as a non-attachment by clearing // opened inline. If so, treat it as a non-attachment by clearing
// 'forceExternalHandling' again. This allows it open a PDF directly // 'forceExternalHandling' again. This allows it open a PDF directly
// instead of downloading it first. It may still end up being handled by // instead of downloading it first. It may still end up being handled by
// a helper app depending anyway on the later checks. // a helper app depending anyway on the later checks.
if (isPDF) { auto result = ShouldHandleExternally(nsLiteralCString(APPLICATION_PDF));
nsCOMPtr<nsILoadInfo> loadInfo; if (result.isErr()) {
aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); return result.unwrapErr();
}
forceExternalHandling = result.unwrap();
nsCOMPtr<nsIMIMEInfo> mimeInfo; // If we're not opening the PDF externally we block it if it's sandboxed.
if (IsSandboxed(aChannel) && !forceExternalHandling) {
nsCOMPtr<nsIMIMEService> mimeSvc( LOG(("Blocked sandboxed PDF"));
do_GetService(NS_MIMESERVICE_CONTRACTID)); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE); if (httpChannel) {
mimeSvc->GetFromTypeAndExtension(nsLiteralCString(APPLICATION_PDF), ""_ns, nsContentSecurityUtils::LogMessageToConsole(
getter_AddRefs(mimeInfo)); httpChannel, "IframeSandboxBlockedDownload");
if (mimeInfo) {
int32_t action = nsIMIMEInfo::saveToDisk;
mimeInfo->GetPreferredAction(&action);
bool alwaysAsk = true;
mimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
forceExternalHandling =
alwaysAsk || action != nsIMIMEInfo::handleInternally;
} }
return NS_ERROR_CONTENT_BLOCKED;
} }
} }