Bug 1833876. r=lsalzman

Differential Revision: https://phabricator.services.mozilla.com/D179483
This commit is contained in:
Andrew Osmond 2023-07-18 16:29:47 +00:00
parent f2e760e8f6
commit 71792018b4
6 changed files with 113 additions and 40 deletions

View File

@ -465,8 +465,12 @@ class AdjustedTargetForFilter {
const gfx::FilterDescription& filter = mCtx->CurrentState().filter;
MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty());
if (filter.mPrimitives.LastElement().IsTainted() && mCtx->mCanvasElement) {
mCtx->mCanvasElement->SetWriteOnly();
if (filter.mPrimitives.LastElement().IsTainted()) {
if (mCtx->mCanvasElement) {
mCtx->mCanvasElement->SetWriteOnly();
} else if (mCtx->mOffscreenCanvas) {
mCtx->mOffscreenCanvas->SetWriteOnly();
}
}
}
@ -5795,9 +5799,10 @@ already_AddRefed<ImageData> CanvasRenderingContext2D::GetImageData(
// Check only if we have a canvas element; if we were created with a docshell,
// then it's special internal use.
// FIXME(aosmond): OffscreenCanvas security check??!
if (IsWriteOnly() ||
(mCanvasElement && !mCanvasElement->CallerCanRead(aCx))) {
(mCanvasElement && !mCanvasElement->CallerCanRead(aSubjectPrincipal)) ||
(mOffscreenCanvas &&
!mOffscreenCanvas->CallerCanRead(aSubjectPrincipal))) {
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
@ -6317,6 +6322,8 @@ void CanvasRenderingContext2D::SetWriteOnly() {
mWriteOnly = true;
if (mCanvasElement) {
mCanvasElement->SetWriteOnly();
} else if (mOffscreenCanvas) {
mOffscreenCanvas->SetWriteOnly();
}
}

View File

@ -373,7 +373,8 @@ void DoDrawImageSecurityCheck(dom::OffscreenCanvas* aOffscreenCanvas,
return;
}
if (aOffscreenCanvas->IsWriteOnly()) {
nsIPrincipal* expandedReader = aOffscreenCanvas->GetExpandedReader();
if (aOffscreenCanvas->IsWriteOnly() && !expandedReader) {
return;
}
@ -401,6 +402,24 @@ void DoDrawImageSecurityCheck(dom::OffscreenCanvas* aOffscreenCanvas,
return;
}
if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
// This is a resource from an extension content script principal.
if (expandedReader && expandedReader->Subsumes(aPrincipal)) {
// This canvas already allows reading from this principal.
return;
}
if (!expandedReader) {
// Allow future reads from this same princial only.
aOffscreenCanvas->SetWriteOnly(aPrincipal);
return;
}
// If we got here, this must be the *second* extension tainting
// the canvas. Fall through to mark it WriteOnly for everyone.
}
aOffscreenCanvas->SetWriteOnly();
}

View File

@ -27,6 +27,7 @@
#include "ImageBitmap.h"
#include "ImageBitmapRenderingContext.h"
#include "nsContentUtils.h"
#include "nsProxyRelease.h"
#include "WebGLChild.h"
namespace mozilla::dom {
@ -34,32 +35,40 @@ namespace mozilla::dom {
OffscreenCanvasCloneData::OffscreenCanvasCloneData(
OffscreenCanvasDisplayHelper* aDisplay, uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend, layers::TextureType aTextureType,
bool aNeutered, bool aIsWriteOnly)
bool aNeutered, bool aIsWriteOnly, nsIPrincipal* aExpandedReader)
: mDisplay(aDisplay),
mWidth(aWidth),
mHeight(aHeight),
mCompositorBackendType(aCompositorBackend),
mTextureType(aTextureType),
mNeutered(aNeutered),
mIsWriteOnly(aIsWriteOnly) {}
mIsWriteOnly(aIsWriteOnly),
mExpandedReader(aExpandedReader) {}
OffscreenCanvasCloneData::~OffscreenCanvasCloneData() = default;
OffscreenCanvasCloneData::~OffscreenCanvasCloneData() {
NS_ReleaseOnMainThread("OffscreenCanvasCloneData::mExpandedReader",
mExpandedReader.forget());
}
OffscreenCanvas::OffscreenCanvas(nsIGlobalObject* aGlobal, uint32_t aWidth,
uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::TextureType aTextureType,
OffscreenCanvasDisplayHelper* aDisplay)
uint32_t aHeight)
: DOMEventTargetHelper(aGlobal), mWidth(aWidth), mHeight(aHeight) {}
OffscreenCanvas::OffscreenCanvas(
nsIGlobalObject* aGlobal, uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend, layers::TextureType aTextureType,
already_AddRefed<OffscreenCanvasDisplayHelper> aDisplay)
: DOMEventTargetHelper(aGlobal),
mNeutered(false),
mIsWriteOnly(false),
mWidth(aWidth),
mHeight(aHeight),
mCompositorBackendType(aCompositorBackend),
mTextureType(aTextureType),
mDisplay(aDisplay) {}
OffscreenCanvas::~OffscreenCanvas() = default;
OffscreenCanvas::~OffscreenCanvas() {
NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
mExpandedReader.forget());
}
JSObject* OffscreenCanvas::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
@ -88,9 +97,8 @@ already_AddRefed<OffscreenCanvas> OffscreenCanvas::Constructor(
}
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<OffscreenCanvas> offscreenCanvas = new OffscreenCanvas(
global, aWidth, aHeight, layers::LayersBackend::LAYERS_NONE,
layers::TextureType::Unknown, nullptr);
RefPtr<OffscreenCanvas> offscreenCanvas =
new OffscreenCanvas(global, aWidth, aHeight);
return offscreenCanvas.forget();
}
@ -275,7 +283,7 @@ void OffscreenCanvas::CommitFrameToCompositor() {
OffscreenCanvasCloneData* OffscreenCanvas::ToCloneData() {
return new OffscreenCanvasCloneData(mDisplay, mWidth, mHeight,
mCompositorBackendType, mTextureType,
mNeutered, mIsWriteOnly);
mNeutered, mIsWriteOnly, mExpandedReader);
}
already_AddRefed<ImageBitmap> OffscreenCanvas::TransferToImageBitmap(
@ -463,6 +471,29 @@ already_AddRefed<gfx::SourceSurface> OffscreenCanvas::GetSurfaceSnapshot(
return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType);
}
void OffscreenCanvas::SetWriteOnly(RefPtr<nsIPrincipal>&& aExpandedReader) {
NS_ReleaseOnMainThread("OffscreenCanvas::mExpandedReader",
mExpandedReader.forget());
mExpandedReader = std::move(aExpandedReader);
mIsWriteOnly = true;
}
bool OffscreenCanvas::CallerCanRead(nsIPrincipal& aPrincipal) const {
if (!mIsWriteOnly) {
return true;
}
// If mExpandedReader is set, this canvas was tainted only by
// mExpandedReader's resources. So allow reading if the subject
// principal subsumes mExpandedReader.
if (mExpandedReader && aPrincipal.Subsumes(mExpandedReader)) {
return true;
}
return nsContentUtils::PrincipalHasPermission(aPrincipal,
nsGkAtoms::all_urlsPermission);
}
bool OffscreenCanvas::ShouldResistFingerprinting(RFPTarget aTarget) const {
return nsContentUtils::ShouldResistFingerprinting(GetOwnerGlobal(), aTarget);
}
@ -473,10 +504,13 @@ already_AddRefed<OffscreenCanvas> OffscreenCanvas::CreateFromCloneData(
MOZ_ASSERT(aData);
RefPtr<OffscreenCanvas> wc = new OffscreenCanvas(
aGlobal, aData->mWidth, aData->mHeight, aData->mCompositorBackendType,
aData->mTextureType, aData->mDisplay);
aData->mTextureType, aData->mDisplay.forget());
if (aData->mNeutered) {
wc->SetNeutered();
}
if (aData->mIsWriteOnly) {
wc->SetWriteOnly(std::move(aData->mExpandedReader));
}
return wc.forget();
}

View File

@ -48,7 +48,7 @@ struct OffscreenCanvasCloneData final {
uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::TextureType aTextureType, bool aNeutered,
bool aIsWriteOnly);
bool aIsWriteOnly, nsIPrincipal* aExpandedReader);
~OffscreenCanvasCloneData();
RefPtr<OffscreenCanvasDisplayHelper> mDisplay;
@ -58,6 +58,7 @@ struct OffscreenCanvasCloneData final {
layers::TextureType mTextureType;
bool mNeutered;
bool mIsWriteOnly;
RefPtr<nsIPrincipal> mExpandedReader;
};
class OffscreenCanvas final : public DOMEventTargetHelper,
@ -70,10 +71,12 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
IMPL_EVENT_HANDLER(contextlost);
IMPL_EVENT_HANDLER(contextrestored);
OffscreenCanvas(nsIGlobalObject* aGlobal, uint32_t aWidth, uint32_t aHeight);
OffscreenCanvas(nsIGlobalObject* aGlobal, uint32_t aWidth, uint32_t aHeight,
layers::LayersBackend aCompositorBackend,
layers::TextureType aTextureType,
OffscreenCanvasDisplayHelper* aDisplay);
already_AddRefed<OffscreenCanvasDisplayHelper> aDisplay);
nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); }
@ -146,10 +149,20 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
bool MayNeuter() const { return !mNeutered && !mCurrentContext; }
void SetWriteOnly() { mIsWriteOnly = true; }
nsIPrincipal* GetExpandedReader() const { return mExpandedReader; }
void SetWriteOnly(RefPtr<nsIPrincipal>&& aExpandedReader);
void SetWriteOnly(nsIPrincipal* aExpandedReader = nullptr) {
RefPtr<nsIPrincipal> expandedReader(aExpandedReader);
SetWriteOnly(std::move(expandedReader));
}
bool IsWriteOnly() const { return mIsWriteOnly; }
// Determines if the caller should be able to read the content.
bool CallerCanRead(nsIPrincipal& aPrincipal) const;
layers::LayersBackend GetCompositorBackendType() const {
return mCompositorBackendType;
}
@ -169,17 +182,19 @@ class OffscreenCanvas final : public DOMEventTargetHelper,
UpdateContext(nullptr, JS::NullHandleValue, dummy);
}
bool mNeutered;
bool mIsWriteOnly;
bool mNeutered = false;
bool mIsWriteOnly = false;
uint32_t mWidth;
uint32_t mHeight;
uint32_t mWidth = 0;
uint32_t mHeight = 0;
layers::LayersBackend mCompositorBackendType;
layers::TextureType mTextureType;
layers::LayersBackend mCompositorBackendType =
layers::LayersBackend::LAYERS_NONE;
layers::TextureType mTextureType = layers::TextureType::Unknown;
RefPtr<OffscreenCanvasDisplayHelper> mDisplay;
RefPtr<CancelableRunnable> mPendingCommit;
RefPtr<nsIPrincipal> mExpandedReader;
Maybe<OffscreenCanvasDisplayData> mPendingUpdate;
};

View File

@ -763,7 +763,7 @@ void HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {
// mWriteOnly check is redundant, but optimizes for the common case.
if (mWriteOnly && !CallerCanRead(aCx)) {
if (mWriteOnly && !CallerCanRead(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
@ -948,7 +948,7 @@ void HTMLCanvasElement::ToBlob(JSContext* aCx, BlobCallback& aCallback,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {
// mWriteOnly check is redundant, but optimizes for the common case.
if (mWriteOnly && !CallerCanRead(aCx)) {
if (mWriteOnly && !CallerCanRead(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
@ -1010,9 +1010,9 @@ OffscreenCanvas* HTMLCanvasElement::TransferControlToOffscreen(
MakeRefPtr<OffscreenCanvasDisplayHelper>(this, sz.width, sz.height);
mOffscreenCanvas =
new OffscreenCanvas(win->AsGlobal(), sz.width, sz.height, backend,
textureType, mOffscreenDisplay);
textureType, do_AddRef(mOffscreenDisplay));
if (mWriteOnly) {
mOffscreenCanvas->SetWriteOnly();
mOffscreenCanvas->SetWriteOnly(mExpandedReader);
}
if (!mContextObserver) {
@ -1053,25 +1053,23 @@ void HTMLCanvasElement::SetWriteOnly(
mExpandedReader = aExpandedReader;
mWriteOnly = true;
if (mOffscreenCanvas) {
mOffscreenCanvas->SetWriteOnly();
mOffscreenCanvas->SetWriteOnly(aExpandedReader);
}
}
bool HTMLCanvasElement::CallerCanRead(JSContext* aCx) const {
bool HTMLCanvasElement::CallerCanRead(nsIPrincipal& aPrincipal) const {
if (!mWriteOnly) {
return true;
}
nsIPrincipal* prin = nsContentUtils::SubjectPrincipal(aCx);
// If mExpandedReader is set, this canvas was tainted only by
// mExpandedReader's resources. So allow reading if the subject
// principal subsumes mExpandedReader.
if (mExpandedReader && prin->Subsumes(mExpandedReader)) {
if (mExpandedReader && aPrincipal.Subsumes(mExpandedReader)) {
return true;
}
return nsContentUtils::PrincipalHasPermission(*prin,
return nsContentUtils::PrincipalHasPermission(aPrincipal,
nsGkAtoms::all_urlsPermission);
}

View File

@ -373,7 +373,7 @@ class HTMLCanvasElement final : public nsGenericHTMLElement,
RefPtr<nsIPrincipal> mExpandedReader;
// Determines if the caller should be able to read the content.
bool CallerCanRead(JSContext* aCx) const;
bool CallerCanRead(nsIPrincipal& aPrincipal) const;
bool IsPrintCallbackDone();