CanvasUtils.cpp (28174B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "CanvasUtils.h" 7 8 #include <stdlib.h> 9 10 #include "WebGL2Context.h" 11 #include "jsapi.h" 12 #include "mozIThirdPartyUtil.h" 13 #include "mozilla/BasePrincipal.h" 14 #include "mozilla/Services.h" 15 #include "mozilla/StaticPrefs_gfx.h" 16 #include "mozilla/StaticPrefs_privacy.h" 17 #include "mozilla/StaticPrefs_webgl.h" 18 #include "mozilla/dom/BrowserChild.h" 19 #include "mozilla/dom/Document.h" 20 #include "mozilla/dom/HTMLCanvasElement.h" 21 #include "mozilla/dom/OffscreenCanvas.h" 22 #include "mozilla/dom/UserActivation.h" 23 #include "mozilla/dom/WindowGlobalParent.h" 24 #include "mozilla/dom/WorkerCommon.h" 25 #include "mozilla/dom/WorkerPrivate.h" 26 #include "mozilla/dom/WorkerRunnable.h" 27 #include "mozilla/gfx/Matrix.h" 28 #include "mozilla/gfx/gfxVars.h" 29 #include "nsContentUtils.h" 30 #include "nsGfxCIID.h" 31 #include "nsICanvasRenderingContextInternal.h" 32 #include "nsIHTMLCollection.h" 33 #include "nsIObserverService.h" 34 #include "nsIPermissionManager.h" 35 #include "nsIPrincipal.h" 36 #include "nsIScriptError.h" 37 #include "nsIScriptObjectPrincipal.h" 38 #include "nsPrintfCString.h" 39 #include "nsTArray.h" 40 #include "nsUnicharUtils.h" 41 42 #define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt" 43 #define TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER \ 44 "canvas-permissions-prompt-hide-doorhanger" 45 #define PERMISSION_CANVAS_EXTRACT_DATA "canvas"_ns 46 47 using namespace mozilla::gfx; 48 49 static bool IsUnrestrictedPrincipal(nsIPrincipal* aPrincipal) { 50 if (!aPrincipal) { 51 return false; 52 } 53 54 // The system principal can always extract canvas data. 55 if (aPrincipal->IsSystemPrincipal()) { 56 return true; 57 } 58 59 // Allow chrome: and resource: (this especially includes PDF.js) 60 if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource")) { 61 return true; 62 } 63 64 // Allow extension principals. 65 return aPrincipal->GetIsAddonOrExpandedAddonPrincipal(); 66 } 67 68 namespace mozilla::CanvasUtils { 69 70 class OffscreenCanvasPermissionRunnable final 71 : public dom::WorkerMainThreadRunnable { 72 public: 73 OffscreenCanvasPermissionRunnable(dom::WorkerPrivate* aWorkerPrivate, 74 nsIPrincipal* aPrincipal) 75 : WorkerMainThreadRunnable(aWorkerPrivate, 76 "OffscreenCanvasPermissionRunnable"_ns), 77 mPrincipal(aPrincipal) { 78 MOZ_ASSERT(aWorkerPrivate); 79 aWorkerPrivate->AssertIsOnWorkerThread(); 80 } 81 82 bool MainThreadRun() override { 83 AssertIsOnMainThread(); 84 85 mResult = GetCanvasExtractDataPermission(mPrincipal); 86 return true; 87 } 88 89 uint32_t GetResult() const { return mResult; } 90 91 private: 92 nsCOMPtr<nsIPrincipal> mPrincipal; 93 uint32_t mResult = nsIPermissionManager::UNKNOWN_ACTION; 94 }; 95 96 uint32_t GetCanvasExtractDataPermission(nsIPrincipal* aPrincipal) { 97 if (!aPrincipal) { 98 return nsIPermissionManager::UNKNOWN_ACTION; 99 } 100 101 if (IsUnrestrictedPrincipal(aPrincipal)) { 102 return true; 103 } 104 105 if (NS_IsMainThread()) { 106 nsresult rv; 107 nsCOMPtr<nsIPermissionManager> permissionManager = 108 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); 109 NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION); 110 111 uint32_t permission; 112 rv = permissionManager->TestPermissionFromPrincipal( 113 aPrincipal, PERMISSION_CANVAS_EXTRACT_DATA, &permission); 114 NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION); 115 116 return permission; 117 } 118 if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) { 119 RefPtr<OffscreenCanvasPermissionRunnable> runnable = 120 new OffscreenCanvasPermissionRunnable(workerPrivate, aPrincipal); 121 ErrorResult rv; 122 runnable->Dispatch(workerPrivate, dom::WorkerStatus::Canceling, rv); 123 if (rv.Failed()) { 124 return nsIPermissionManager::UNKNOWN_ACTION; 125 } 126 return runnable->GetResult(); 127 } 128 return nsIPermissionManager::UNKNOWN_ACTION; 129 } 130 131 /* 132 ┌──────────────────────────────────────────────────────────────────────────┐ 133 │IsImageExtractionAllowed(dom::OffscreenCanvas*, JSContext*, nsIPrincipal*)│ 134 └────────────────────────────────────┬─────────────────────────────────────┘ 135 │ 136 ┌─────────────────▼────────────────────┐ 137 ┌─────No──────────│Any prompt RFP target enabled? See [1]│ 138 ▼ └─────────────────┬────────────────────┘ 139 │ │Yes 140 │ ┌─────────────────▼────────┐ 141 ├─────Yes─────────┤Is unrestricted principal?│ 142 ▼ └─────────────────┬────────┘ 143 │ │No 144 │ ┌─────────────────▼────────┐ 145 │ ┌──No──┤Are third parties blocked?│ 146 │ │ └─────────────────┬────────┘ 147 │ │ │Yes 148 │ │ ┌─────────────────▼─────────────┐ 149 │ │ │Are we in a third-party window?├───────Yes──────────┐ 150 │ │ └─────────────────┬─────────────┘ ▼ 151 │ │ │No │ 152 │ │ ┌─────────────────▼──┐ │ 153 │ └──────►Do we show a prompt?├────────────Yes─┐ │ 154 │ └─────────────────┬──┘ ▼ │ 155 │ │No │ │ 156 │ ┌─────────────────▼─────────────┐ │ │ 157 │ │Do we allow reading canvas data│ │ │ 158 │ │in response to user input? ├─No──┤ │ 159 │ └─────────────────┬─────────────┘ ▼ │ 160 │ │Yes │ │ 161 │ ┌─────────────────▼─────────┐ │ │ 162 ├─────Yes─────────┼Are we handling user input?│ │ │ 163 ▼ └─────────────────┬─────────┘ │ │ 164 │ │No │ │ 165 │ ┌─────────────────▼─────────────┐ │ │ 166 ┌▼─────┐ │Show Permission Prompt (either ◄─────┘ ┌───▼──┐ 167 │return│ │w/ doorhanger, or w/o depending│ │return│ 168 │true │ │on User Input) ├────────────────►false │ 169 └──────┘ └───────────────────────────────┘ └──────┘ 170 [1]: CanvasImageExtractionPrompt, CanvasExtractionBeforeUserInputIsBlocked, 171 CanvasExtractionFromThirdPartiesIsBlocked are the RFP targets mentioned. 172 */ 173 bool IsImageExtractionAllowed_impl( 174 bool aCanvasImageExtractionPrompt, 175 bool aCanvasExtractionBeforeUserInputIsBlocked, 176 bool aCanvasExtractionFromThirdPartiesIsBlocked, JSContext* aCx, 177 nsIPrincipal* aPrincipal, 178 const std::function<bool()>& aGetIsThirdPartyWindow, 179 const std::function<void(const nsAutoString&)>& aReportToConsole, 180 const std::function<void(bool)>& aTryPrompt) { 181 /* 182 * There are three RFPTargets that change the behavior here, and they can be 183 * in any combination 184 * - CanvasImageExtractionPrompt - whether or not to prompt the user for 185 * canvas extraction. If enabled, before canvas is extracted we will ensure 186 * the user has granted permission. 187 * - CanvasExtractionBeforeUserInputIsBlocked - if enabled, canvas extraction 188 * before user input has occurred is always blocked, regardless of any other 189 * Target behavior 190 * - CanvasExtractionFromThirdPartiesIsBlocked - if enabled, canvas extraction 191 * by third parties is always blocked, regardless of any other Target behavior 192 * 193 * There are two odd cases: 194 * 1) When CanvasImageExtractionPrompt=false but 195 * CanvasExtractionBeforeUserInputIsBlocked=true Conceptually this is 196 * "Always allow canvas extraction in response to user input, and never 197 * allow it otherwise" 198 * 199 * That's fine as a concept, but it might be a little confusing, so we 200 * still want to show the permission icon in the address bar, but never 201 * the permission doorhanger. 202 * 2) When CanvasExtractionFromThirdPartiesIsBlocked=false - we will prompt 203 * the user for permission _for the frame_ (maybe with the doorhanger, 204 * maybe not). The prompt shows the frame's origin, but it's easy to 205 * mistake that for the origin of the top-level page and grant it when you 206 * don't mean to. This combination isn't likely to be used by anyone 207 * except those opting in, so that's alright. 208 */ 209 210 if (!aCanvasImageExtractionPrompt && 211 !aCanvasExtractionBeforeUserInputIsBlocked && 212 !aCanvasExtractionFromThirdPartiesIsBlocked) { 213 return true; 214 } 215 216 // Don't proceed if we don't have a document or JavaScript context. 217 if (!aCx) { 218 return false; 219 } 220 221 if (IsUnrestrictedPrincipal(aPrincipal)) { 222 return true; 223 } 224 225 Maybe<nsAutoCString> origin = Nothing(); 226 auto getOrigin = [&]() { 227 if (origin.isSome()) { 228 return origin->IsEmpty(); 229 } 230 231 nsAutoCString originResult; 232 nsresult rv = NS_ERROR_FAILURE; 233 if (aPrincipal) { 234 rv = aPrincipal->GetOrigin(originResult); 235 } 236 origin = NS_SUCCEEDED(rv) ? Some(originResult) : Some(""_ns); 237 238 return NS_SUCCEEDED(rv); 239 }; 240 241 if (aCanvasExtractionFromThirdPartiesIsBlocked) { 242 if (aGetIsThirdPartyWindow()) { 243 nsAutoString message; 244 message.AppendPrintf( 245 "Blocked %s third party from extracting canvas data.", 246 getOrigin() ? origin->get() : "unknown"); 247 aReportToConsole(message); 248 return false; 249 } 250 } 251 252 if (!aCanvasImageExtractionPrompt && 253 !aCanvasExtractionBeforeUserInputIsBlocked) { 254 return true; 255 } 256 257 // ------------------------------------------------------------------- 258 // Check a site's permission 259 260 // If the user has previously granted or not granted permission, we can return 261 // immediately. Load Permission Manager service. 262 uint64_t permission = GetCanvasExtractDataPermission(aPrincipal); 263 switch (permission) { 264 case nsIPermissionManager::ALLOW_ACTION: 265 return true; 266 case nsIPermissionManager::DENY_ACTION: 267 return false; 268 default: 269 break; 270 } 271 272 // ------------------------------------------------------------------- 273 // At this point, there's only one way to return true: if we are always 274 // allowing canvas in response to user input, and not prompting 275 bool hidePermissionDoorhanger = false; 276 if (!aCanvasImageExtractionPrompt && 277 aCanvasExtractionBeforeUserInputIsBlocked) { 278 // If so, see if this is in response to user input. 279 if (NS_IsMainThread() && dom::UserActivation::IsHandlingUserInput()) { 280 return true; 281 } 282 283 hidePermissionDoorhanger = true; 284 } 285 286 // ------------------------------------------------------------------- 287 // Now we know we're going to block it, and log something to the console, 288 // and show some sort of prompt maybe with the doorhanger, maybe not 289 290 hidePermissionDoorhanger |= 291 aCanvasExtractionBeforeUserInputIsBlocked && 292 (!NS_IsMainThread() || !dom::UserActivation::IsHandlingUserInput()); 293 294 nsAutoString message; 295 message.AppendPrintf("Blocked %s from extracting canvas data", 296 getOrigin() ? origin->get() : "unknown"); 297 message.AppendPrintf(hidePermissionDoorhanger 298 ? " because no user input was detected" 299 : " but prompting the user."); 300 aReportToConsole(message); 301 302 aTryPrompt(hidePermissionDoorhanger); 303 304 return false; 305 } 306 307 bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx, 308 nsIPrincipal* aPrincipal) { 309 if (NS_WARN_IF(!aDocument)) { 310 return false; 311 } 312 313 bool canvasImageExtractionPrompt = aDocument->ShouldResistFingerprinting( 314 RFPTarget::CanvasImageExtractionPrompt); 315 bool canvasExtractionBeforeUserInputIsBlocked = 316 aDocument->ShouldResistFingerprinting( 317 RFPTarget::CanvasExtractionBeforeUserInputIsBlocked); 318 bool canvasExtractionFromThirdPartiesIsBlocked = 319 aDocument->ShouldResistFingerprinting( 320 RFPTarget::CanvasExtractionFromThirdPartiesIsBlocked); 321 322 // This part is duplicate but it helps us return faster 323 // before we create bunch of lambdas 324 if (!canvasImageExtractionPrompt && 325 !canvasExtractionBeforeUserInputIsBlocked && 326 !canvasExtractionFromThirdPartiesIsBlocked) { 327 return true; 328 } 329 330 auto getIsThirdPartyWindow = [&]() { 331 return aDocument->GetWindowContext() 332 ? aDocument->GetWindowContext()->GetIsThirdPartyWindow() 333 : false; 334 }; 335 336 auto reportToConsole = [&](const nsAutoString& message) { 337 nsContentUtils::ReportToConsoleNonLocalized( 338 message, nsIScriptError::warningFlag, "Security"_ns, aDocument); 339 }; 340 341 auto prompt = [&](bool hidePermissionDoorhanger) { 342 if (!aPrincipal) { 343 return; 344 } 345 346 nsAutoCString origin; 347 nsresult rv = aPrincipal->GetOrigin(origin); 348 if (NS_FAILED(rv)) { 349 return; 350 } 351 352 if (!XRE_IsContentProcess()) { 353 MOZ_ASSERT_UNREACHABLE( 354 "Who's calling this from the parent process without a chrome window " 355 "(it would have been exempt from the RFP targets)?"); 356 return; 357 } 358 359 nsPIDOMWindowOuter* win = aDocument->GetWindow(); 360 if (RefPtr<dom::BrowserChild> browserChild = 361 dom::BrowserChild::GetFrom(win)) { 362 browserChild->SendShowCanvasPermissionPrompt(origin, 363 hidePermissionDoorhanger); 364 } 365 }; 366 367 return IsImageExtractionAllowed_impl( 368 canvasImageExtractionPrompt, canvasExtractionBeforeUserInputIsBlocked, 369 canvasExtractionFromThirdPartiesIsBlocked, aCx, aPrincipal, 370 getIsThirdPartyWindow, reportToConsole, prompt); 371 } 372 373 ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement, 374 JSContext* aCx, 375 nsIPrincipal* aPrincipal) { 376 if (IsUnrestrictedPrincipal(aPrincipal)) { 377 return ImageExtraction::Unrestricted; 378 } 379 380 nsCOMPtr<dom::Document> ownerDoc = aCanvasElement->OwnerDoc(); 381 if (!IsImageExtractionAllowed(ownerDoc, aCx, aPrincipal)) { 382 return ImageExtraction::Placeholder; 383 } 384 385 if (ownerDoc->ShouldResistFingerprinting( 386 RFPTarget::EfficientCanvasRandomization) && 387 GetCanvasExtractDataPermission(aPrincipal) != 388 nsIPermissionManager::ALLOW_ACTION) { 389 return ImageExtraction::EfficientRandomize; 390 } 391 392 if ((ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization) || 393 ownerDoc->ShouldResistFingerprinting(RFPTarget::WebGLRandomization)) && 394 GetCanvasExtractDataPermission(aPrincipal) != 395 nsIPermissionManager::ALLOW_ACTION) { 396 return ImageExtraction::Randomize; 397 } 398 399 return ImageExtraction::Unrestricted; 400 } 401 402 bool IsImageExtractionAllowed(dom::OffscreenCanvas* aOffscreenCanvas, 403 JSContext* aCx, nsIPrincipal* aPrincipal) { 404 if (!aOffscreenCanvas) { 405 return false; 406 } 407 408 bool canvasImageExtractionPrompt = 409 aOffscreenCanvas->ShouldResistFingerprinting( 410 RFPTarget::CanvasImageExtractionPrompt); 411 bool canvasExtractionBeforeUserInputIsBlocked = 412 aOffscreenCanvas->ShouldResistFingerprinting( 413 RFPTarget::CanvasExtractionBeforeUserInputIsBlocked); 414 bool canvasExtractionFromThirdPartiesIsBlocked = 415 aOffscreenCanvas->ShouldResistFingerprinting( 416 RFPTarget::CanvasExtractionFromThirdPartiesIsBlocked); 417 418 // This part is duplicate but it helps us return faster 419 // before we create bunch of lambdas 420 if (!canvasImageExtractionPrompt && 421 !canvasExtractionBeforeUserInputIsBlocked && 422 !canvasExtractionFromThirdPartiesIsBlocked) { 423 return true; 424 } 425 426 Maybe<uint64_t> winId = aOffscreenCanvas->GetWindowID(); 427 if (winId.isSome() && *winId == UINT64_MAX) { 428 // Workers with no window return UINT64_MAX as their window ID. 429 winId = Nothing(); 430 } 431 432 auto getIsThirdPartyWindow = [&]() { 433 if (winId.isNothing()) { 434 return false; 435 } 436 437 if (NS_IsMainThread()) { 438 if (RefPtr<dom::WindowContext> win = 439 dom::WindowGlobalParent::GetById(*winId)) { 440 return win->GetIsThirdPartyWindow(); 441 } 442 } else if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) { 443 return workerPrivate->IsThirdPartyContext(); 444 } 445 446 return false; 447 }; 448 449 auto reportToConsole = [&](const nsAutoString& message) { 450 if (winId.isNothing()) { 451 return; 452 } 453 454 nsContentUtils::ReportToConsoleByWindowID( 455 message, nsIScriptError::warningFlag, "Security"_ns, *winId); 456 }; 457 458 nsAutoCString origin; 459 if (!aPrincipal || NS_FAILED(aPrincipal->GetOrigin(origin))) { 460 origin = ""_ns; 461 } 462 463 RefPtr<dom::OffscreenCanvas> canvasRef = aOffscreenCanvas; 464 auto prompt = [=](bool hidePermissionDoorhanger) { 465 if (origin.IsEmpty()) { 466 return; 467 } 468 469 if (!XRE_IsContentProcess()) { 470 MOZ_ASSERT_UNREACHABLE( 471 "Who's calling this from the parent process without a chrome " 472 "window " 473 "(it would have been exempt from the RFP targets)?"); 474 return; 475 } 476 477 if (NS_IsMainThread()) { 478 nsCOMPtr<nsIGlobalObject> global = canvasRef->GetOwnerGlobal(); 479 NS_ENSURE_TRUE_VOID(global); 480 481 RefPtr<nsPIDOMWindowInner> window = global->GetAsInnerWindow(); 482 NS_ENSURE_TRUE_VOID(window); 483 484 RefPtr<dom::BrowserChild> browserChild = 485 dom::BrowserChild::GetFrom(window); 486 NS_ENSURE_TRUE_VOID(browserChild); 487 488 browserChild->SendShowCanvasPermissionPrompt(origin, 489 hidePermissionDoorhanger); 490 return; 491 } 492 493 class OffscreenCanvasPromptRunnable 494 : public dom::WorkerProxyToMainThreadRunnable { 495 public: 496 explicit OffscreenCanvasPromptRunnable(const nsCString& aOrigin, 497 bool aHidePermissionDoorhanger) 498 : mOrigin(aOrigin), 499 mHidePermissionDoorhanger(aHidePermissionDoorhanger) {} 500 501 // Runnables don't support MOZ_CAN_RUN_SCRIPT, bug 1535398 502 MOZ_CAN_RUN_SCRIPT_BOUNDARY void RunOnMainThread( 503 dom::WorkerPrivate* aWorkerPrivate) override { 504 MOZ_ASSERT(aWorkerPrivate); 505 AssertIsOnMainThread(); 506 507 RefPtr<nsPIDOMWindowInner> inner = aWorkerPrivate->GetAncestorWindow(); 508 RefPtr<dom::BrowserChild> win = dom::BrowserChild::GetFrom(inner); 509 NS_ENSURE_TRUE_VOID(win); 510 511 win->SendShowCanvasPermissionPrompt(mOrigin, mHidePermissionDoorhanger); 512 } 513 514 void RunBackOnWorkerThreadForCleanup( 515 dom::WorkerPrivate* aWorkerPrivate) override { 516 MOZ_ASSERT(aWorkerPrivate); 517 aWorkerPrivate->AssertIsOnWorkerThread(); 518 } 519 520 nsCString mOrigin; 521 bool mHidePermissionDoorhanger; 522 }; 523 524 if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) { 525 RefPtr<OffscreenCanvasPromptRunnable> runnable = 526 new OffscreenCanvasPromptRunnable(origin, hidePermissionDoorhanger); 527 runnable->Dispatch(workerPrivate); 528 return; 529 } 530 }; 531 532 return IsImageExtractionAllowed_impl( 533 canvasImageExtractionPrompt, canvasExtractionBeforeUserInputIsBlocked, 534 canvasExtractionFromThirdPartiesIsBlocked, aCx, aPrincipal, 535 getIsThirdPartyWindow, reportToConsole, prompt); 536 } 537 538 ImageExtraction ImageExtractionResult(dom::OffscreenCanvas* aOffscreenCanvas, 539 JSContext* aCx, 540 nsIPrincipal* aPrincipal) { 541 if (IsUnrestrictedPrincipal(aPrincipal)) { 542 return ImageExtraction::Unrestricted; 543 } 544 545 if (!IsImageExtractionAllowed(aOffscreenCanvas, aCx, aPrincipal)) { 546 return ImageExtraction::Placeholder; 547 } 548 549 if (aOffscreenCanvas->ShouldResistFingerprinting( 550 RFPTarget::CanvasRandomization) || 551 aOffscreenCanvas->ShouldResistFingerprinting( 552 RFPTarget::WebGLRandomization)) { 553 if (GetCanvasExtractDataPermission(aPrincipal) == 554 nsIPermissionManager::ALLOW_ACTION) { 555 return ImageExtraction::Unrestricted; 556 } 557 return ImageExtraction::Randomize; 558 } 559 560 return ImageExtraction::Unrestricted; 561 } 562 563 bool GetCanvasContextType(const nsAString& str, 564 dom::CanvasContextType* const out_type) { 565 if (str.EqualsLiteral("2d")) { 566 *out_type = dom::CanvasContextType::Canvas2D; 567 return true; 568 } 569 570 if (str.EqualsLiteral("webgl") || str.EqualsLiteral("experimental-webgl")) { 571 *out_type = dom::CanvasContextType::WebGL1; 572 return true; 573 } 574 575 if (StaticPrefs::webgl_enable_webgl2()) { 576 if (str.EqualsLiteral("webgl2")) { 577 *out_type = dom::CanvasContextType::WebGL2; 578 return true; 579 } 580 } 581 582 if (gfxVars::AllowWebGPU()) { 583 if (str.EqualsLiteral("webgpu")) { 584 *out_type = dom::CanvasContextType::WebGPU; 585 return true; 586 } 587 } 588 589 if (str.EqualsLiteral("bitmaprenderer")) { 590 *out_type = dom::CanvasContextType::ImageBitmap; 591 return true; 592 } 593 594 return false; 595 } 596 597 /** 598 * This security check utility might be called from an source that never 599 * taints others. For example, while painting a CanvasPattern, which is 600 * created from an ImageBitmap, onto a canvas. In this case, the caller could 601 * set the CORSUsed true in order to pass this check and leave the aPrincipal 602 * to be a nullptr since the aPrincipal is not going to be used. 603 */ 604 void DoDrawImageSecurityCheck(dom::HTMLCanvasElement* aCanvasElement, 605 nsIPrincipal* aPrincipal, bool forceWriteOnly, 606 bool CORSUsed) { 607 // Callers should ensure that mCanvasElement is non-null before calling this 608 if (!aCanvasElement) { 609 NS_WARNING("DoDrawImageSecurityCheck called without canvas element!"); 610 return; 611 } 612 613 if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) { 614 return; 615 } 616 617 // If we explicitly set WriteOnly just do it and get out 618 if (forceWriteOnly) { 619 aCanvasElement->SetWriteOnly(); 620 return; 621 } 622 623 // No need to do a security check if the image used CORS for the load 624 if (CORSUsed) return; 625 626 if (NS_WARN_IF(!aPrincipal)) { 627 MOZ_ASSERT_UNREACHABLE("Must have a principal here"); 628 aCanvasElement->SetWriteOnly(); 629 return; 630 } 631 632 if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) { 633 // This canvas has access to that image anyway 634 return; 635 } 636 637 if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { 638 // This is a resource from an extension content script principal. 639 640 if (aCanvasElement->mExpandedReader && 641 aCanvasElement->mExpandedReader->Subsumes(aPrincipal)) { 642 // This canvas already allows reading from this principal. 643 return; 644 } 645 646 if (!aCanvasElement->mExpandedReader) { 647 // Allow future reads from this same princial only. 648 aCanvasElement->SetWriteOnly(aPrincipal); 649 return; 650 } 651 652 // If we got here, this must be the *second* extension tainting 653 // the canvas. Fall through to mark it WriteOnly for everyone. 654 } 655 656 aCanvasElement->SetWriteOnly(); 657 } 658 659 /** 660 * This security check utility might be called from an source that never 661 * taints others. For example, while painting a CanvasPattern, which is 662 * created from an ImageBitmap, onto a canvas. In this case, the caller could 663 * set the aCORSUsed true in order to pass this check and leave the aPrincipal 664 * to be a nullptr since the aPrincipal is not going to be used. 665 */ 666 void DoDrawImageSecurityCheck(dom::OffscreenCanvas* aOffscreenCanvas, 667 nsIPrincipal* aPrincipal, bool aForceWriteOnly, 668 bool aCORSUsed) { 669 // Callers should ensure that mCanvasElement is non-null before calling this 670 if (NS_WARN_IF(!aOffscreenCanvas)) { 671 return; 672 } 673 674 nsIPrincipal* expandedReader = aOffscreenCanvas->GetExpandedReader(); 675 if (aOffscreenCanvas->IsWriteOnly() && !expandedReader) { 676 return; 677 } 678 679 // If we explicitly set WriteOnly just do it and get out 680 if (aForceWriteOnly) { 681 aOffscreenCanvas->SetWriteOnly(); 682 return; 683 } 684 685 // No need to do a security check if the image used CORS for the load 686 if (aCORSUsed) { 687 return; 688 } 689 690 // If we are on a worker thread, we might not have any principals at all. 691 nsIGlobalObject* global = aOffscreenCanvas->GetOwnerGlobal(); 692 nsIPrincipal* canvasPrincipal = global ? global->PrincipalOrNull() : nullptr; 693 if (!aPrincipal || !canvasPrincipal) { 694 aOffscreenCanvas->SetWriteOnly(); 695 return; 696 } 697 698 if (canvasPrincipal->Subsumes(aPrincipal)) { 699 // This canvas has access to that image anyway 700 return; 701 } 702 703 if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { 704 // This is a resource from an extension content script principal. 705 706 if (expandedReader && expandedReader->Subsumes(aPrincipal)) { 707 // This canvas already allows reading from this principal. 708 return; 709 } 710 711 if (!expandedReader) { 712 // Allow future reads from this same princial only. 713 aOffscreenCanvas->SetWriteOnly(aPrincipal); 714 return; 715 } 716 717 // If we got here, this must be the *second* extension tainting 718 // the canvas. Fall through to mark it WriteOnly for everyone. 719 } 720 721 aOffscreenCanvas->SetWriteOnly(); 722 } 723 724 bool CoerceDouble(const JS::Value& v, double* d) { 725 if (v.isDouble()) { 726 *d = v.toDouble(); 727 } else if (v.isInt32()) { 728 *d = double(v.toInt32()); 729 } else if (v.isUndefined()) { 730 *d = 0.0; 731 } else { 732 return false; 733 } 734 return true; 735 } 736 737 bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* /* unused */) { 738 return nsContentUtils::CallerHasPermission(aCx, 739 nsGkAtoms::all_urlsPermission); 740 } 741 742 bool CheckWriteOnlySecurity(bool aCORSUsed, nsIPrincipal* aPrincipal, 743 bool aHadCrossOriginRedirects) { 744 if (!aPrincipal) { 745 return true; 746 } 747 748 if (!aCORSUsed) { 749 if (aHadCrossOriginRedirects) { 750 return true; 751 } 752 753 nsIGlobalObject* incumbentSettingsObject = dom::GetIncumbentGlobal(); 754 if (!incumbentSettingsObject) { 755 return true; 756 } 757 758 nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull(); 759 if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) { 760 return true; 761 } 762 } 763 764 return false; 765 } 766 767 } // namespace mozilla::CanvasUtils