Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: inject.preload.js

Issue 29586710: Issue 5382 - Wrap DOM mutation APIs to protect frames (Closed) Base URL: https://hg.adblockplus.org/adblockpluschrome/
Left Patch Set: Created Oct. 23, 2017, 10:09 p.m.
Right Patch Set: Bind function to Function.prototype.apply and handle missing descriptors Created Feb. 21, 2018, 4:31 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-present eyeo GmbH 3 * Copyright (C) 2006-present eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
65 contentWindow[eventName] = checkRequest; 65 contentWindow[eventName] = checkRequest;
66 contentWindow.eval( 66 contentWindow.eval(
67 "(" + injectedToString() + ")('" + eventName + "', true);" 67 "(" + injectedToString() + ")('" + eventName + "', true);"
68 ); 68 );
69 delete contentWindow[eventName]; 69 delete contentWindow[eventName];
70 } 70 }
71 catch (e) {} 71 catch (e) {}
72 } 72 }
73 } 73 }
74 74
75 function wrapAPIForIFrame(object, api) 75 function injectIntoAllFrames()
76 {
77 for (let i = 0; i < window.length; i++)
78 injectIntoContentWindow(window[i]);
79 }
80
81 function wrapAPIForInjection(object, api, callback)
76 { 82 {
77 let func = object[api]; 83 let func = object[api];
78 object[api] = function(...args) 84 if (!func || typeof func != "function")
79 { 85 return;
80 let returnValue = func.apply(this, args); 86 let applyFunc = Function.prototype.apply.bind(func);
81 for (let i = 0; i < window.length; i++) 87 Object.defineProperty(object, api, {
82 injectIntoContentWindow(window[i]); 88 value(...args)
83 return returnValue; 89 {
84 }; 90 let returnValue = applyFunc(this, args);
85 } 91 callback(returnValue);
86 92 return returnValue;
87 function wrapPropertyAPIForIFrame(object, api) 93 }
94 });
95 }
96
97 function wrapPropertyAPIForInjection(object, api, method, callback)
88 { 98 {
89 let descriptor = Object.getOwnPropertyDescriptor(object, api); 99 let descriptor = Object.getOwnPropertyDescriptor(object, api);
90 wrapAPIForIFrame(descriptor, "set"); 100 // Apparently HTMLObjectElement.prototype.contentWindow does not exist in
101 // older versions of Chrome such as 42.
102 if (!descriptor)
103 return;
104 wrapAPIForInjection(descriptor, method, callback);
91 Object.defineProperty(object, api, descriptor); 105 Object.defineProperty(object, api, descriptor);
92 } 106 }
93 107
94 wrapAPIForIFrame(Node.prototype, "appendChild"); 108 wrapAPIForInjection(Node.prototype, "appendChild", injectIntoAllFrames);
95 wrapAPIForIFrame(Node.prototype, "insertBefore"); 109 wrapAPIForInjection(Node.prototype, "insertBefore", injectIntoAllFrames);
96 wrapAPIForIFrame(Node.prototype, "replaceChild"); 110 wrapAPIForInjection(Node.prototype, "replaceChild", injectIntoAllFrames);
97 111
98 wrapPropertyAPIForIFrame(Element.prototype, "innerHTML"); 112 wrapPropertyAPIForInjection(Element.prototype,
99 113 "innerHTML", "set", injectIntoAllFrames);
100 for (let element of [HTMLFrameElement, HTMLIFrameElement, HTMLObjectElement]) 114
101 { 115 wrapPropertyAPIForInjection(HTMLObjectElement.prototype,
102 let contentDocumentDesc = Object.getOwnPropertyDescriptor( 116 "contentWindow", "get", injectIntoContentWindow);
103 element.prototype, "contentDocument" 117 wrapPropertyAPIForInjection(
104 ); 118 HTMLObjectElement.prototype,
105 let contentWindowDesc = Object.getOwnPropertyDescriptor( 119 "contentDocument", "get",
106 element.prototype, "contentWindow" 120 contentDocument =>
107 ); 121 {
108 122 if (contentDocument)
109 // Apparently in HTMLObjectElement.prototype.contentWindow does not exist 123 injectIntoContentWindow(contentDocument.defaultView);
110 // in older versions of Chrome such as 42. 124 }
111 if (!contentWindowDesc) 125 );
112 continue;
113
114 let getContentDocument = Function.prototype.call.bind(
115 contentDocumentDesc.get
116 );
117 let getContentWindow = Function.prototype.call.bind(
118 contentWindowDesc.get
119 );
120
121 contentWindowDesc.get = function()
122 {
123 let contentWindow = getContentWindow(this);
124 injectIntoContentWindow(contentWindow);
125 return contentWindow;
126 };
127 contentDocumentDesc.get = function()
128 {
129 injectIntoContentWindow(getContentWindow(this));
130 return getContentDocument(this);
131 };
132 Object.defineProperty(element.prototype, "contentWindow",
133 contentWindowDesc);
134 Object.defineProperty(element.prototype, "contentDocument",
135 contentDocumentDesc);
136 }
137 126
138 /* 127 /*
139 * Shadow root getter wrapper 128 * Shadow root getter wrapper
140 * 129 *
141 * After creating our shadowRoot we must wrap the getter to prevent the 130 * After creating our shadowRoot we must wrap the getter to prevent the
142 * website from accessing it (#4191, #4298). This is required as a 131 * website from accessing it (#4191, #4298). This is required as a
143 * workaround for the lack of user style support in Chrome. 132 * workaround for the lack of user style support in Chrome.
144 * See https://bugs.chromium.org/p/chromium/issues/detail?id=632009&desc=2 133 * See https://bugs.chromium.org/p/chromium/issues/detail?id=632009&desc=2
145 */ 134 */
146 if ("shadowRoot" in Element.prototype) 135 if ("shadowRoot" in Element.prototype)
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
243 232
244 /* 233 /*
245 * RTCPeerConnection wrapper 234 * RTCPeerConnection wrapper
246 * 235 *
247 * The webRequest API in Chrome does not yet allow the blocking of 236 * The webRequest API in Chrome does not yet allow the blocking of
248 * WebRTC connections. 237 * WebRTC connections.
249 * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683 238 * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683
250 */ 239 */
251 let RealRTCPeerConnection = window.RTCPeerConnection || 240 let RealRTCPeerConnection = window.RTCPeerConnection ||
252 window.webkitRTCPeerConnection; 241 window.webkitRTCPeerConnection;
253 let closeRTCPeerConnection = Function.prototype.call.bind( 242
254 RealRTCPeerConnection.prototype.close 243 // Firefox has the option (media.peerconnection.enabled) to disable WebRTC
255 ); 244 // in which case RealRTCPeerConnection is undefined.
256 let RealArray = Array; 245 if (typeof RealRTCPeerConnection != "undefined")
257 let RealString = String; 246 {
258 let {create: createObject, defineProperty} = Object; 247 let closeRTCPeerConnection = Function.prototype.call.bind(
259 248 RealRTCPeerConnection.prototype.close
260 function normalizeUrl(url) 249 );
261 { 250 let RealArray = Array;
262 if (typeof url != "undefined") 251 let RealString = String;
263 return RealString(url); 252 let {create: createObject, defineProperty} = Object;
264 } 253
265 254 let normalizeUrl = url =>
266 function safeCopyArray(originalArray, transform) 255 {
267 { 256 if (typeof url != "undefined")
268 if (originalArray == null || typeof originalArray != "object") 257 return RealString(url);
269 return originalArray; 258 };
270 259
271 let safeArray = RealArray(originalArray.length); 260 let safeCopyArray = (originalArray, transform) =>
272 for (let i = 0; i < safeArray.length; i++) 261 {
273 { 262 if (originalArray == null || typeof originalArray != "object")
274 defineProperty(safeArray, i, { 263 return originalArray;
264
265 let safeArray = RealArray(originalArray.length);
266 for (let i = 0; i < safeArray.length; i++)
267 {
268 defineProperty(safeArray, i, {
269 configurable: false, enumerable: false, writable: false,
270 value: transform(originalArray[i])
271 });
272 }
273 defineProperty(safeArray, "length", {
275 configurable: false, enumerable: false, writable: false, 274 configurable: false, enumerable: false, writable: false,
276 value: transform(originalArray[i]) 275 value: safeArray.length
277 }); 276 });
278 } 277 return safeArray;
279 defineProperty(safeArray, "length", { 278 };
280 configurable: false, enumerable: false, writable: false, 279
281 value: safeArray.length 280 // It would be much easier to use the .getConfiguration method to obtain
282 }); 281 // the normalized and safe configuration from the RTCPeerConnection
283 return safeArray; 282 // instance. Unfortunately its not implemented as of Chrome unstable 59.
284 } 283 // See https://www.chromestatus.com/feature/5271355306016768
285 284 let protectConfiguration = configuration =>
286 // It would be much easier to use the .getConfiguration method to obtain 285 {
287 // the normalized and safe configuration from the RTCPeerConnection 286 if (configuration == null || typeof configuration != "object")
288 // instance. Unfortunately its not implemented as of Chrome unstable 59. 287 return configuration;
289 // See https://www.chromestatus.com/feature/5271355306016768 288
290 function protectConfiguration(configuration) 289 let iceServers = safeCopyArray(
291 { 290 configuration.iceServers,
292 if (configuration == null || typeof configuration != "object") 291 iceServer =>
293 return configuration; 292 {
294 293 let {url, urls} = iceServer;
295 let iceServers = safeCopyArray( 294
296 configuration.iceServers, 295 // RTCPeerConnection doesn't iterate through pseudo Arrays of urls.
297 iceServer => 296 if (typeof urls != "undefined" && !(urls instanceof RealArray))
298 { 297 urls = [urls];
299 let {url, urls} = iceServer; 298
300 299 return createObject(iceServer, {
301 // RTCPeerConnection doesn't iterate through pseudo Arrays of urls. 300 url: {
302 if (typeof urls != "undefined" && !(urls instanceof RealArray)) 301 configurable: false, enumerable: false, writable: false,
303 urls = [urls]; 302 value: normalizeUrl(url)
304 303 },
305 return createObject(iceServer, { 304 urls: {
306 url: { 305 configurable: false, enumerable: false, writable: false,
307 configurable: false, enumerable: false, writable: false, 306 value: safeCopyArray(urls, normalizeUrl)
308 value: normalizeUrl(url) 307 }
309 }, 308 });
310 urls: { 309 }
311 configurable: false, enumerable: false, writable: false, 310 );
312 value: safeCopyArray(urls, normalizeUrl) 311
312 return createObject(configuration, {
313 iceServers: {
314 configurable: false, enumerable: false, writable: false,
315 value: iceServers
316 }
317 });
318 };
319
320 let checkUrl = (peerconnection, url) =>
321 {
322 checkRequest("webrtc", url, blocked =>
323 {
324 if (blocked)
325 {
326 // Calling .close() throws if already closed.
327 try
328 {
329 closeRTCPeerConnection(peerconnection);
313 } 330 }
314 }); 331 catch (e) {}
315 } 332 }
316 ); 333 });
317 334 };
318 return createObject(configuration, { 335
319 iceServers: { 336 let checkConfiguration = (peerconnection, configuration) =>
320 configurable: false, enumerable: false, writable: false, 337 {
321 value: iceServers 338 if (configuration && configuration.iceServers)
322 } 339 {
323 }); 340 for (let i = 0; i < configuration.iceServers.length; i++)
324 }
325
326 function checkUrl(peerconnection, url)
327 {
328 checkRequest("webrtc", url, blocked =>
329 {
330 if (blocked)
331 {
332 // Calling .close() throws if already closed.
333 try
334 { 341 {
335 closeRTCPeerConnection(peerconnection); 342 let iceServer = configuration.iceServers[i];
336 } 343 if (iceServer)
337 catch (e) {}
338 }
339 });
340 }
341
342 function checkConfiguration(peerconnection, configuration)
343 {
344 if (configuration && configuration.iceServers)
345 {
346 for (let i = 0; i < configuration.iceServers.length; i++)
347 {
348 let iceServer = configuration.iceServers[i];
349 if (iceServer)
350 {
351 if (iceServer.url)
352 checkUrl(peerconnection, iceServer.url);
353
354 if (iceServer.urls)
355 { 344 {
356 for (let j = 0; j < iceServer.urls.length; j++) 345 if (iceServer.url)
357 checkUrl(peerconnection, iceServer.urls[j]); 346 checkUrl(peerconnection, iceServer.url);
347
348 if (iceServer.urls)
349 {
350 for (let j = 0; j < iceServer.urls.length; j++)
351 checkUrl(peerconnection, iceServer.urls[j]);
352 }
358 } 353 }
359 } 354 }
360 } 355 }
356 };
357
358 // Chrome unstable (tested with 59) has already implemented
359 // setConfiguration, so we need to wrap that if it exists too.
360 // https://www.chromestatus.com/feature/5596193748942848
361 if (RealRTCPeerConnection.prototype.setConfiguration)
362 {
363 let realSetConfiguration = Function.prototype.call.bind(
364 RealRTCPeerConnection.prototype.setConfiguration
365 );
366
367 RealRTCPeerConnection.prototype.setConfiguration = function(configuration)
368 {
369 configuration = protectConfiguration(configuration);
370
371 // Call the real method first, so that validates the configuration for
372 // us. Also we might as well since checkRequest is asynchronous anyway.
373 realSetConfiguration(this, configuration);
374 checkConfiguration(this, configuration);
375 };
361 } 376 }
362 } 377
363 378 let WrappedRTCPeerConnection = function(...args)
364 // Chrome unstable (tested with 59) has already implemented 379 {
365 // setConfiguration, so we need to wrap that if it exists too. 380 if (!(this instanceof WrappedRTCPeerConnection))
366 // https://www.chromestatus.com/feature/5596193748942848 381 return RealRTCPeerConnection();
367 if (RealRTCPeerConnection.prototype.setConfiguration) 382
368 { 383 let configuration = protectConfiguration(args[0]);
369 let realSetConfiguration = Function.prototype.call.bind( 384
370 RealRTCPeerConnection.prototype.setConfiguration 385 // Since the old webkitRTCPeerConnection constructor takes an optional
371 ); 386 // second argument we need to take care to pass that through. Necessary
372 387 // for older versions of Chrome such as 49.
373 RealRTCPeerConnection.prototype.setConfiguration = function(configuration) 388 let constraints = undefined;
374 { 389 if (args.length > 1)
375 configuration = protectConfiguration(configuration); 390 constraints = args[1];
376 391
377 // Call the real method first, so that validates the configuration for 392 let peerconnection = new RealRTCPeerConnection(configuration,
378 // us. Also we might as well since checkRequest is asynchronous anyway. 393 constraints);
379 realSetConfiguration(this, configuration); 394 checkConfiguration(peerconnection, configuration);
380 checkConfiguration(this, configuration); 395 return peerconnection;
381 }; 396 };
382 } 397
383 398 WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;
384 function WrappedRTCPeerConnection(...args) 399
385 { 400 let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
386 if (!(this instanceof WrappedRTCPeerConnection)) 401 copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
387 return RealRTCPeerConnection(); 402 ["generateCertificate", "name", "prototype"]);
388 403 RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;
389 let configuration = protectConfiguration(args[0]); 404
390 405 if ("RTCPeerConnection" in window)
391 // Since the old webkitRTCPeerConnection constructor takes an optional 406 window.RTCPeerConnection = boundWrappedRTCPeerConnection;
392 // second argument we need to take care to pass that through. Necessary 407 if ("webkitRTCPeerConnection" in window)
393 // for older versions of Chrome such as 49. 408 window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
394 let constraints = undefined; 409 }
395 if (args.length > 1)
396 constraints = args[1];
397
398 let peerconnection = new RealRTCPeerConnection(configuration, constraints);
399 checkConfiguration(peerconnection, configuration);
400 return peerconnection;
401 }
402
403 WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;
404
405 let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
406 copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
407 ["generateCertificate", "name", "prototype"]);
408 RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;
409
410 if ("RTCPeerConnection" in window)
411 window.RTCPeerConnection = boundWrappedRTCPeerConnection;
412 if ("webkitRTCPeerConnection" in window)
413 window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
414 } 410 }
415 411
416 if (document instanceof HTMLDocument) 412 if (document instanceof HTMLDocument)
417 { 413 {
418 let sandbox = window.frameElement && 414 let sandbox = window.frameElement &&
419 window.frameElement.getAttribute("sandbox"); 415 window.frameElement.getAttribute("sandbox");
420 416
421 if (typeof sandbox != "string" || /(^|\s)allow-scripts(\s|$)/i.test(sandbox)) 417 if (typeof sandbox != "string" || /(^|\s)allow-scripts(\s|$)/i.test(sandbox))
422 { 418 {
423 let script = document.createElement("script"); 419 let script = document.createElement("script");
424 script.type = "application/javascript"; 420 script.type = "application/javascript";
425 script.async = false; 421 script.async = false;
426 script.textContent = "(" + injected + ")('" + randomEventName + "');"; 422 // Firefox 58 only bypasses site CSPs when assigning to 'src'.
423 let url = URL.createObjectURL(new Blob([
424 "(" + injected + ")('" + randomEventName + "');"
425 ]));
426 script.src = url;
427 document.documentElement.appendChild(script); 427 document.documentElement.appendChild(script);
428 document.documentElement.removeChild(script); 428 document.documentElement.removeChild(script);
429 URL.revokeObjectURL(url);
429 } 430 }
430 } 431 }
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld