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 <stdlib.h>
7 #include <stdarg.h>
8
9 #include "nsICanvasRenderingContextInternal.h"
10 #include "nsIHTMLCollection.h"
11 #include "mozilla/dom/BrowserChild.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/HTMLCanvasElement.h"
14 #include "mozilla/dom/UserActivation.h"
15 #include "mozilla/BasePrincipal.h"
16 #include "mozilla/StaticPrefs_dom.h"
17 #include "mozilla/StaticPrefs_privacy.h"
18 #include "mozilla/StaticPrefs_webgl.h"
19 #include "nsIPrincipal.h"
20
21 #include "nsGfxCIID.h"
22
23 #include "nsTArray.h"
24
25 #include "CanvasUtils.h"
26 #include "mozilla/gfx/Matrix.h"
27 #include "WebGL2Context.h"
28
29 #include "nsIScriptError.h"
30 #include "nsIScriptObjectPrincipal.h"
31 #include "nsIPermissionManager.h"
32 #include "nsIObserverService.h"
33 #include "mozilla/Services.h"
34 #include "mozIThirdPartyUtil.h"
35 #include "nsContentUtils.h"
36 #include "nsUnicharUtils.h"
37 #include "nsPrintfCString.h"
38 #include "jsapi.h"
39
40 #define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt"
41 #define TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER \
42 "canvas-permissions-prompt-hide-doorhanger"
43 #define PERMISSION_CANVAS_EXTRACT_DATA "canvas"_ns
44
45 using namespace mozilla::gfx;
46
47 namespace mozilla::CanvasUtils {
48
IsImageExtractionAllowed(dom::Document * aDocument,JSContext * aCx,nsIPrincipal & aPrincipal)49 bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
50 nsIPrincipal& aPrincipal) {
51 // Do the rest of the checks only if privacy.resistFingerprinting is on.
52 if (!nsContentUtils::ShouldResistFingerprinting(aDocument)) {
53 return true;
54 }
55
56 // Don't proceed if we don't have a document or JavaScript context.
57 if (!aDocument || !aCx) {
58 return false;
59 }
60
61 // The system principal can always extract canvas data.
62 if (aPrincipal.IsSystemPrincipal()) {
63 return true;
64 }
65
66 // Allow extension principals.
67 auto principal = BasePrincipal::Cast(&aPrincipal);
68 if (principal->AddonPolicy() || principal->ContentScriptAddonPolicy()) {
69 return true;
70 }
71
72 // Get the document URI and its spec.
73 nsIURI* docURI = aDocument->GetDocumentURI();
74 nsCString docURISpec;
75 docURI->GetSpec(docURISpec);
76
77 // Allow local files to extract canvas data.
78 if (docURI->SchemeIs("file")) {
79 return true;
80 }
81
82 // Don't show canvas prompt for PDF.js
83 JS::AutoFilename scriptFile;
84 if (JS::DescribeScriptedCaller(aCx, &scriptFile) && scriptFile.get() &&
85 strcmp(scriptFile.get(), "resource://pdf.js/build/pdf.js") == 0) {
86 return true;
87 }
88
89 dom::Document* topLevelDocument =
90 aDocument->GetTopLevelContentDocumentIfSameProcess();
91 nsIURI* topLevelDocURI =
92 topLevelDocument ? topLevelDocument->GetDocumentURI() : nullptr;
93 nsCString topLevelDocURISpec;
94 if (topLevelDocURI) {
95 topLevelDocURI->GetSpec(topLevelDocURISpec);
96 }
97
98 // Load Third Party Util service.
99 nsresult rv;
100 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
101 do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
102 NS_ENSURE_SUCCESS(rv, false);
103
104 // Block all third-party attempts to extract canvas.
105 bool isThirdParty = true;
106 rv = thirdPartyUtil->IsThirdPartyURI(topLevelDocURI, docURI, &isThirdParty);
107 NS_ENSURE_SUCCESS(rv, false);
108 if (isThirdParty) {
109 nsAutoString message;
110 message.AppendPrintf("Blocked third party %s from extracting canvas data.",
111 docURISpec.get());
112 nsContentUtils::ReportToConsoleNonLocalized(
113 message, nsIScriptError::warningFlag, "Security"_ns, aDocument);
114 return false;
115 }
116
117 // Load Permission Manager service.
118 nsCOMPtr<nsIPermissionManager> permissionManager =
119 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
120 NS_ENSURE_SUCCESS(rv, false);
121
122 // Check if the site has permission to extract canvas data.
123 // Either permit or block extraction if a stored permission setting exists.
124 uint32_t permission;
125 rv = permissionManager->TestPermissionFromPrincipal(
126 principal, PERMISSION_CANVAS_EXTRACT_DATA, &permission);
127 NS_ENSURE_SUCCESS(rv, false);
128 switch (permission) {
129 case nsIPermissionManager::ALLOW_ACTION:
130 return true;
131 case nsIPermissionManager::DENY_ACTION:
132 return false;
133 default:
134 break;
135 }
136
137 // At this point, permission is unknown
138 // (nsIPermissionManager::UNKNOWN_ACTION).
139
140 // Check if the request is in response to user input
141 bool isAutoBlockCanvas =
142 StaticPrefs::
143 privacy_resistFingerprinting_autoDeclineNoUserInputCanvasPrompts() &&
144 !dom::UserActivation::IsHandlingUserInput();
145
146 if (isAutoBlockCanvas) {
147 nsAutoString message;
148 message.AppendPrintf(
149 "Blocked %s from extracting canvas data because no user input was "
150 "detected.",
151 docURISpec.get());
152 nsContentUtils::ReportToConsoleNonLocalized(
153 message, nsIScriptError::warningFlag, "Security"_ns, aDocument);
154 } else {
155 // It was in response to user input, so log and display the prompt.
156 nsAutoString message;
157 message.AppendPrintf(
158 "Blocked %s from extracting canvas data, but prompting the user.",
159 docURISpec.get());
160 nsContentUtils::ReportToConsoleNonLocalized(
161 message, nsIScriptError::warningFlag, "Security"_ns, aDocument);
162 }
163
164 // Prompt the user (asynchronous).
165 nsPIDOMWindowOuter* win = aDocument->GetWindow();
166 nsAutoCString origin;
167 rv = principal->GetOrigin(origin);
168 NS_ENSURE_SUCCESS(rv, false);
169
170 if (XRE_IsContentProcess()) {
171 dom::BrowserChild* browserChild = dom::BrowserChild::GetFrom(win);
172 if (browserChild) {
173 browserChild->SendShowCanvasPermissionPrompt(origin, isAutoBlockCanvas);
174 }
175 } else {
176 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
177 if (obs) {
178 obs->NotifyObservers(win,
179 isAutoBlockCanvas
180 ? TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER
181 : TOPIC_CANVAS_PERMISSIONS_PROMPT,
182 NS_ConvertUTF8toUTF16(origin).get());
183 }
184 }
185
186 // We don't extract the image for now -- user may override at prompt.
187 return false;
188 }
189
GetCanvasContextType(const nsAString & str,dom::CanvasContextType * const out_type)190 bool GetCanvasContextType(const nsAString& str,
191 dom::CanvasContextType* const out_type) {
192 if (str.EqualsLiteral("2d")) {
193 *out_type = dom::CanvasContextType::Canvas2D;
194 return true;
195 }
196
197 if (str.EqualsLiteral("webgl") || str.EqualsLiteral("experimental-webgl")) {
198 *out_type = dom::CanvasContextType::WebGL1;
199 return true;
200 }
201
202 if (StaticPrefs::webgl_enable_webgl2()) {
203 if (str.EqualsLiteral("webgl2")) {
204 *out_type = dom::CanvasContextType::WebGL2;
205 return true;
206 }
207 }
208
209 if (StaticPrefs::dom_webgpu_enabled()) {
210 if (str.EqualsLiteral("gpupresent")) {
211 *out_type = dom::CanvasContextType::WebGPU;
212 return true;
213 }
214 }
215
216 if (str.EqualsLiteral("bitmaprenderer")) {
217 *out_type = dom::CanvasContextType::ImageBitmap;
218 return true;
219 }
220
221 return false;
222 }
223
224 /**
225 * This security check utility might be called from an source that never taints
226 * others. For example, while painting a CanvasPattern, which is created from an
227 * ImageBitmap, onto a canvas. In this case, the caller could set the CORSUsed
228 * true in order to pass this check and leave the aPrincipal to be a nullptr
229 * since the aPrincipal is not going to be used.
230 */
DoDrawImageSecurityCheck(dom::HTMLCanvasElement * aCanvasElement,nsIPrincipal * aPrincipal,bool forceWriteOnly,bool CORSUsed)231 void DoDrawImageSecurityCheck(dom::HTMLCanvasElement* aCanvasElement,
232 nsIPrincipal* aPrincipal, bool forceWriteOnly,
233 bool CORSUsed) {
234 // Callers should ensure that mCanvasElement is non-null before calling this
235 if (!aCanvasElement) {
236 NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
237 return;
238 }
239
240 if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) {
241 return;
242 }
243
244 // If we explicitly set WriteOnly just do it and get out
245 if (forceWriteOnly) {
246 aCanvasElement->SetWriteOnly();
247 return;
248 }
249
250 // No need to do a security check if the image used CORS for the load
251 if (CORSUsed) return;
252
253 MOZ_ASSERT(aPrincipal, "Must have a principal here");
254
255 if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) {
256 // This canvas has access to that image anyway
257 return;
258 }
259
260 if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
261 // This is a resource from an extension content script principal.
262
263 if (aCanvasElement->mExpandedReader &&
264 aCanvasElement->mExpandedReader->Subsumes(aPrincipal)) {
265 // This canvas already allows reading from this principal.
266 return;
267 }
268
269 if (!aCanvasElement->mExpandedReader) {
270 // Allow future reads from this same princial only.
271 aCanvasElement->SetWriteOnly(aPrincipal);
272 return;
273 }
274
275 // If we got here, this must be the *second* extension tainting
276 // the canvas. Fall through to mark it WriteOnly for everyone.
277 }
278
279 aCanvasElement->SetWriteOnly();
280 }
281
CoerceDouble(const JS::Value & v,double * d)282 bool CoerceDouble(const JS::Value& v, double* d) {
283 if (v.isDouble()) {
284 *d = v.toDouble();
285 } else if (v.isInt32()) {
286 *d = double(v.toInt32());
287 } else if (v.isUndefined()) {
288 *d = 0.0;
289 } else {
290 return false;
291 }
292 return true;
293 }
294
HasDrawWindowPrivilege(JSContext * aCx,JSObject *)295 bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* /* unused */) {
296 return nsContentUtils::CallerHasPermission(aCx,
297 nsGkAtoms::all_urlsPermission);
298 }
299
CheckWriteOnlySecurity(bool aCORSUsed,nsIPrincipal * aPrincipal,bool aHadCrossOriginRedirects)300 bool CheckWriteOnlySecurity(bool aCORSUsed, nsIPrincipal* aPrincipal,
301 bool aHadCrossOriginRedirects) {
302 if (!aPrincipal) {
303 return true;
304 }
305
306 if (!aCORSUsed) {
307 if (aHadCrossOriginRedirects) {
308 return true;
309 }
310
311 nsIGlobalObject* incumbentSettingsObject = dom::GetIncumbentGlobal();
312 if (!incumbentSettingsObject) {
313 return true;
314 }
315
316 nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull();
317 if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) {
318 return true;
319 }
320 }
321
322 return false;
323 }
324
325 } // namespace mozilla::CanvasUtils
326