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