Bug 1918690: Check canvas permission in RandomizePixels function. a=pascalc

Differential Revision: https://phabricator.services.mozilla.com/D255970
This commit is contained in:
Fatih Kilic
2025-07-09 08:54:13 +00:00
committed by pchevrel@mozilla.com
parent 8f6956fb31
commit 922992a8c2
12 changed files with 74 additions and 36 deletions

View File

@@ -2144,8 +2144,9 @@ UniquePtr<uint8_t[]> CanvasRenderingContext2D::GetImageBuffer(
if (ret && ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
nsRFPService::RandomizePixels(
GetCookieJarSettings(), ret.get(), out_imageSize->width,
out_imageSize->height, out_imageSize->width * out_imageSize->height * 4,
GetCookieJarSettings(), PrincipalOrNull(), ret.get(),
out_imageSize->width, out_imageSize->height,
out_imageSize->width * out_imageSize->height * 4,
SurfaceFormat::A8R8G8B8_UINT32);
}
@@ -6407,9 +6408,10 @@ nsresult CanvasRenderingContext2D::GetImageDataArray(
// holder.
const IntSize size = readback->GetSize();
nsRFPService::RandomizePixels(
GetCookieJarSettings(), rawData.mData, size.width, size.height,
size.height * size.width * 4, SurfaceFormat::A8R8G8B8_UINT32);
nsRFPService::RandomizePixels(GetCookieJarSettings(), PrincipalOrNull(),
rawData.mData, size.width, size.height,
size.height * size.width * 4,
SurfaceFormat::A8R8G8B8_UINT32);
}
JS::AutoCheckCannotGC nogc;

View File

@@ -322,10 +322,6 @@ ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement,
}
if (ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
if (GetCanvasExtractDataPermission(aPrincipal) ==
nsIPermissionManager::ALLOW_ACTION) {
return ImageExtraction::Unrestricted;
}
return ImageExtraction::Randomize;
}

View File

@@ -59,10 +59,16 @@ bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* aObj);
// Check if the context has permission to use OffscreenCanvas.
bool IsOffscreenCanvasEnabled(JSContext* aCx, JSObject* aObj);
// Check if the principal has "canvas" permission.
uint32_t GetCanvasExtractDataPermission(nsIPrincipal& aPrincipal);
// Check site-specific permission and display prompt if appropriate.
bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
nsIPrincipal& aPrincipal);
bool IsImageExtractionAllowed(dom::OffscreenCanvas* aOffscreenCanvas,
JSContext* aCx, nsIPrincipal& aPrincipal);
uint32_t GetCanvasExtractDataPermission(nsIPrincipal& aPrincipal);
enum class ImageExtraction {

View File

@@ -13,6 +13,7 @@
#include "HostWebGLContext.h"
#include "js/PropertyAndElement.h" // JS_DefineElement
#include "js/ScalarType.h" // js::Scalar::Type
#include "mozilla/dom/CanvasUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TypedArray.h"
@@ -35,6 +36,7 @@
#include "mozilla/StaticPrefs_webgl.h"
#include "nsContentUtils.h"
#include "nsDisplayList.h"
#include "nsIPermissionManager.h"
#include "TexUnpackBlob.h"
#include "WebGLFormats.h"
#include "WebGLMethodDispatcher.h"
@@ -1344,7 +1346,8 @@ UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
return gfxUtils::GetImageBufferWithRandomNoise(
dataSurface, premultAlpha, GetCookieJarSettings(), out_format);
dataSurface, premultAlpha, GetCookieJarSettings(), PrincipalOrNull(),
out_format);
}
return gfxUtils::GetImageBuffer(dataSurface, premultAlpha, out_format);
@@ -1365,7 +1368,7 @@ ClientWebGLContext::GetInputStream(const char* mimeType,
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
return gfxUtils::GetInputStreamWithRandomNoise(
dataSurface, premultAlpha, mimeType, encoderOptions,
GetCookieJarSettings(), out_stream);
GetCookieJarSettings(), PrincipalOrNull(), out_stream);
}
return gfxUtils::GetInputStream(dataSurface, premultAlpha, mimeType,

View File

@@ -195,8 +195,8 @@ mozilla::UniquePtr<uint8_t[]> ImageBitmapRenderingContext::GetImageBuffer(
if (ret && ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
nsRFPService::RandomizePixels(
GetCookieJarSettings(), ret.get(), data->GetSize().width,
data->GetSize().height,
GetCookieJarSettings(), PrincipalOrNull(), ret.get(),
data->GetSize().width, data->GetSize().height,
data->GetSize().width * data->GetSize().height * 4,
gfx::SurfaceFormat::A8R8G8B8_UINT32);
}

View File

@@ -26,6 +26,7 @@
#include "ImageBitmap.h"
#include "ImageBitmapRenderingContext.h"
#include "nsContentUtils.h"
#include "nsIPermissionManager.h"
#include "nsProxyRelease.h"
#include "WebGLChild.h"
@@ -503,8 +504,11 @@ already_AddRefed<Promise> OffscreenCanvas::ConvertToBlob(
RefPtr<EncodeCompleteCallback> callback =
CreateEncodeCompleteCallback(promise);
bool usePlaceholder =
ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt);
bool usePlaceholder = mCurrentContext && mCurrentContext->PrincipalOrNull() &&
!CanvasUtils::IsImageExtractionAllowed(
this, nsContentUtils::GetCurrentJSContext(),
*mCurrentContext->PrincipalOrNull());
CanvasRenderingContextHelper::ToBlob(callback, type, encodeOptions,
/* aUsingCustomOptions */ false,
usePlaceholder, aRv);
@@ -545,8 +549,10 @@ already_AddRefed<Promise> OffscreenCanvas::ToBlob(JSContext* aCx,
RefPtr<EncodeCompleteCallback> callback =
CreateEncodeCompleteCallback(promise);
bool usePlaceholder =
ShouldResistFingerprinting(RFPTarget::CanvasImageExtractionPrompt);
bool usePlaceholder = mCurrentContext && mCurrentContext->PrincipalOrNull() &&
!CanvasUtils::IsImageExtractionAllowed(
this, nsContentUtils::GetCurrentJSContext(),
*mCurrentContext->PrincipalOrNull());
CanvasRenderingContextHelper::ToBlob(aCx, callback, aType, aParams,
usePlaceholder, aRv);

View File

@@ -577,9 +577,21 @@ UniquePtr<uint8_t[]> OffscreenCanvasDisplayHelper::GetImageBuffer(
}
if (resistFingerprinting) {
nsIPrincipal* principal;
{
MutexAutoLock lock(mMutex);
if (mCanvasElement) {
principal = mCanvasElement->NodePrincipal();
}
if (mOffscreenCanvas) {
principal = mOffscreenCanvas->GetParentObject()
? mOffscreenCanvas->GetParentObject()->PrincipalOrNull()
: nullptr;
}
}
nsRFPService::RandomizePixels(
cookieJarSettings, imageBuffer.get(), dataSurface->GetSize().width,
dataSurface->GetSize().height,
cookieJarSettings, principal, imageBuffer.get(),
dataSurface->GetSize().width, dataSurface->GetSize().height,
dataSurface->GetSize().width * dataSurface->GetSize().height * 4,
gfx::SurfaceFormat::A8R8G8B8_UINT32);
}

View File

@@ -326,9 +326,10 @@ mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer(
*out_imageSize = dataSurface->GetSize();
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
gfxUtils::GetImageBufferWithRandomNoise(
dataSurface,
/* aIsAlphaPremultiplied */ true, GetCookieJarSettings(), &*out_format);
gfxUtils::GetImageBufferWithRandomNoise(dataSurface,
/* aIsAlphaPremultiplied */ true,
GetCookieJarSettings(),
PrincipalOrNull(), &*out_format);
}
return gfxUtils::GetImageBuffer(dataSurface, /* aIsAlphaPremultiplied */ true,
@@ -347,9 +348,9 @@ NS_IMETHODIMP CanvasContext::GetInputStream(const char* aMimeType,
RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
gfxUtils::GetInputStreamWithRandomNoise(
return gfxUtils::GetInputStreamWithRandomNoise(
dataSurface, /* aIsAlphaPremultiplied */ true, aMimeType,
aEncoderOptions, GetCookieJarSettings(), aStream);
aEncoderOptions, GetCookieJarSettings(), PrincipalOrNull(), aStream);
}
return gfxUtils::GetInputStream(dataSurface, /* aIsAlphaPremultiplied */ true,

View File

@@ -1613,13 +1613,14 @@ UniquePtr<uint8_t[]> gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface,
/* static */
UniquePtr<uint8_t[]> gfxUtils::GetImageBufferWithRandomNoise(
gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat) {
nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal,
int32_t* outFormat) {
UniquePtr<uint8_t[]> imageBuffer =
GetImageBuffer(aSurface, aIsAlphaPremultiplied, outFormat);
nsRFPService::RandomizePixels(
aCookieJarSettings, imageBuffer.get(), aSurface->GetSize().width,
aSurface->GetSize().height,
aCookieJarSettings, aPrincipal, imageBuffer.get(),
aSurface->GetSize().width, aSurface->GetSize().height,
aSurface->GetSize().width * aSurface->GetSize().height * 4,
SurfaceFormat::A8R8G8B8_UINT32);
@@ -1651,7 +1652,8 @@ nsresult gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface,
nsresult gfxUtils::GetInputStreamWithRandomNoise(
gfx::DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
const char* aMimeType, const nsAString& aEncoderOptions,
nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream) {
nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal,
nsIInputStream** outStream) {
nsCString enccid("@mozilla.org/image/encoder;2?type=");
enccid += aMimeType;
nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
@@ -1667,8 +1669,8 @@ nsresult gfxUtils::GetInputStreamWithRandomNoise(
}
nsRFPService::RandomizePixels(
aCookieJarSettings, imageBuffer.get(), aSurface->GetSize().width,
aSurface->GetSize().height,
aCookieJarSettings, aPrincipal, imageBuffer.get(),
aSurface->GetSize().width, aSurface->GetSize().height,
aSurface->GetSize().width * aSurface->GetSize().height * 4,
SurfaceFormat::A8R8G8B8_UINT32);

View File

@@ -400,7 +400,8 @@ class gfxUtils {
static mozilla::UniquePtr<uint8_t[]> GetImageBufferWithRandomNoise(
DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat);
nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal,
int32_t* outFormat);
static nsresult GetInputStream(DataSourceSurface* aSurface,
bool aIsAlphaPremultiplied,
@@ -411,7 +412,8 @@ class gfxUtils {
static nsresult GetInputStreamWithRandomNoise(
DataSourceSurface* aSurface, bool aIsAlphaPremultiplied,
const char* aMimeType, const nsAString& aEncoderOptions,
nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream);
nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal,
nsIInputStream** outStream);
static void RemoveShaderCacheFromDiskIfNecessary();

View File

@@ -54,6 +54,7 @@
#include "mozilla/dom/MediaDeviceInfoBinding.h"
#include "mozilla/fallible.h"
#include "mozilla/XorShift128PlusRNG.h"
#include "mozilla/dom/CanvasUtils.h"
#include "nsAboutProtocolUtils.h"
#include "nsBaseHashtable.h"
@@ -1708,8 +1709,9 @@ nsresult nsRFPService::GenerateCanvasKeyFromImageData(
// static
nsresult nsRFPService::RandomizePixels(nsICookieJarSettings* aCookieJarSettings,
uint8_t* aData, uint32_t aWidth,
uint32_t aHeight, uint32_t aSize,
nsIPrincipal* aPrincipal, uint8_t* aData,
uint32_t aWidth, uint32_t aHeight,
uint32_t aSize,
gfx::SurfaceFormat aSurfaceFormat) {
NS_ENSURE_ARG_POINTER(aData);
@@ -1721,6 +1723,11 @@ nsresult nsRFPService::RandomizePixels(nsICookieJarSettings* aCookieJarSettings,
return NS_OK;
}
if (aPrincipal && CanvasUtils::GetCanvasExtractDataPermission(*aPrincipal) ==
nsIPermissionManager::ALLOW_ACTION) {
return NS_OK;
}
// Don't randomize if all pixels are uniform.
static constexpr size_t bytesPerPixel = 4;
MOZ_ASSERT(aSize == aWidth * aHeight * bytesPerPixel,

View File

@@ -342,8 +342,9 @@ class nsRFPService final : public nsIObserver, public nsIRFPService {
// The method to add random noises to the image data based on the random key
// of the given cookieJarSettings.
static nsresult RandomizePixels(nsICookieJarSettings* aCookieJarSettings,
uint8_t* aData, uint32_t aWidth,
uint32_t aHeight, uint32_t aSize,
nsIPrincipal* aPrincipal, uint8_t* aData,
uint32_t aWidth, uint32_t aHeight,
uint32_t aSize,
mozilla::gfx::SurfaceFormat aSurfaceFormat);
// --------------------------------------------------------------------------