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: Refactor Created Oct. 24, 2017, 1:39 a.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 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 74
75 function injectIntoAllFrames() 75 function injectIntoAllFrames()
76 { 76 {
77 for (let i = 0; i < window.length; i++) 77 for (let i = 0; i < window.length; i++)
78 injectIntoContentWindow(window[i]); 78 injectIntoContentWindow(window[i]);
79 } 79 }
80 80
81 function wrapAPIForInjection(object, api, callback) 81 function wrapAPIForInjection(object, api, callback)
82 { 82 {
83 let func = object[api]; 83 let func = object[api];
84 object[api] = function(...args) 84 if (!func || typeof func != "function")
85 { 85 return;
86 let returnValue = func.apply(this, args); 86 let applyFunc = Function.prototype.apply.bind(func);
kzar 2017/12/11 08:48:13 I think func.apply could have been messed with at
Manish Jethani 2018/02/21 16:37:39 Thanks, for this one in particular now I've bound
87 callback(returnValue); 87 Object.defineProperty(object, api, {
88 return returnValue; 88 value(...args)
89 }; 89 {
90 let returnValue = applyFunc(this, args);
91 callback(returnValue);
92 return returnValue;
93 }
94 });
90 } 95 }
91 96
92 function wrapPropertyAPIForInjection(object, api, method, callback) 97 function wrapPropertyAPIForInjection(object, api, method, callback)
93 { 98 {
94 let descriptor = Object.getOwnPropertyDescriptor(object, api); 99 let descriptor = Object.getOwnPropertyDescriptor(object, api);
100 // Apparently HTMLObjectElement.prototype.contentWindow does not exist in
101 // older versions of Chrome such as 42.
102 if (!descriptor)
103 return;
95 wrapAPIForInjection(descriptor, method, callback); 104 wrapAPIForInjection(descriptor, method, callback);
96 Object.defineProperty(object, api, descriptor); 105 Object.defineProperty(object, api, descriptor);
97 } 106 }
98 107
99 wrapAPIForInjection(Node.prototype, "appendChild", injectIntoAllFrames); 108 wrapAPIForInjection(Node.prototype, "appendChild", injectIntoAllFrames);
100 wrapAPIForInjection(Node.prototype, "insertBefore", injectIntoAllFrames); 109 wrapAPIForInjection(Node.prototype, "insertBefore", injectIntoAllFrames);
101 wrapAPIForInjection(Node.prototype, "replaceChild", injectIntoAllFrames); 110 wrapAPIForInjection(Node.prototype, "replaceChild", injectIntoAllFrames);
102 111
103 wrapPropertyAPIForInjection(Element.prototype, 112 wrapPropertyAPIForInjection(Element.prototype,
104 "innerHTML", "set", injectIntoAllFrames); 113 "innerHTML", "set", injectIntoAllFrames);
105 114
106 wrapPropertyAPIForInjection(HTMLObjectElement.prototype, 115 wrapPropertyAPIForInjection(HTMLObjectElement.prototype,
Manish Jethani 2017/10/24 01:43:28 HTMLFrameElement and HTMLIFrameElement don't need
kzar 2017/12/11 08:48:13 Fair enough but did you check that it's OK with Ch
Manish Jethani 2018/02/21 16:37:39 I've added a check now so if the descriptor is not
107 "contentWindow", "get", injectIntoContentWindow); 116 "contentWindow", "get", injectIntoContentWindow);
108 wrapPropertyAPIForInjection(HTMLObjectElement.prototype, 117 wrapPropertyAPIForInjection(
109 "contentDocument", "get", 118 HTMLObjectElement.prototype,
110 doc => injectIntoContentWindow(doc.defaultView)); 119 "contentDocument", "get",
120 contentDocument =>
121 {
122 if (contentDocument)
123 injectIntoContentWindow(contentDocument.defaultView);
124 }
125 );
111 126
112 /* 127 /*
113 * Shadow root getter wrapper 128 * Shadow root getter wrapper
114 * 129 *
115 * 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
116 * website from accessing it (#4191, #4298). This is required as a 131 * website from accessing it (#4191, #4298). This is required as a
117 * workaround for the lack of user style support in Chrome. 132 * workaround for the lack of user style support in Chrome.
118 * 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
119 */ 134 */
120 if ("shadowRoot" in Element.prototype) 135 if ("shadowRoot" in Element.prototype)
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
217 232
218 /* 233 /*
219 * RTCPeerConnection wrapper 234 * RTCPeerConnection wrapper
220 * 235 *
221 * 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
222 * WebRTC connections. 237 * WebRTC connections.
223 * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683 238 * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683
224 */ 239 */
225 let RealRTCPeerConnection = window.RTCPeerConnection || 240 let RealRTCPeerConnection = window.RTCPeerConnection ||
226 window.webkitRTCPeerConnection; 241 window.webkitRTCPeerConnection;
227 let closeRTCPeerConnection = Function.prototype.call.bind( 242
228 RealRTCPeerConnection.prototype.close 243 // Firefox has the option (media.peerconnection.enabled) to disable WebRTC
229 ); 244 // in which case RealRTCPeerConnection is undefined.
230 let RealArray = Array; 245 if (typeof RealRTCPeerConnection != "undefined")
231 let RealString = String; 246 {
232 let {create: createObject, defineProperty} = Object; 247 let closeRTCPeerConnection = Function.prototype.call.bind(
233 248 RealRTCPeerConnection.prototype.close
234 function normalizeUrl(url) 249 );
235 { 250 let RealArray = Array;
236 if (typeof url != "undefined") 251 let RealString = String;
237 return RealString(url); 252 let {create: createObject, defineProperty} = Object;
238 } 253
239 254 let normalizeUrl = url =>
240 function safeCopyArray(originalArray, transform) 255 {
241 { 256 if (typeof url != "undefined")
242 if (originalArray == null || typeof originalArray != "object") 257 return RealString(url);
243 return originalArray; 258 };
244 259
245 let safeArray = RealArray(originalArray.length); 260 let safeCopyArray = (originalArray, transform) =>
246 for (let i = 0; i < safeArray.length; i++) 261 {
247 { 262 if (originalArray == null || typeof originalArray != "object")
248 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", {
249 configurable: false, enumerable: false, writable: false, 274 configurable: false, enumerable: false, writable: false,
250 value: transform(originalArray[i]) 275 value: safeArray.length
251 }); 276 });
252 } 277 return safeArray;
253 defineProperty(safeArray, "length", { 278 };
254 configurable: false, enumerable: false, writable: false, 279
255 value: safeArray.length 280 // It would be much easier to use the .getConfiguration method to obtain
256 }); 281 // the normalized and safe configuration from the RTCPeerConnection
257 return safeArray; 282 // instance. Unfortunately its not implemented as of Chrome unstable 59.
258 } 283 // See https://www.chromestatus.com/feature/5271355306016768
259 284 let protectConfiguration = configuration =>
260 // It would be much easier to use the .getConfiguration method to obtain 285 {
261 // the normalized and safe configuration from the RTCPeerConnection 286 if (configuration == null || typeof configuration != "object")
262 // instance. Unfortunately its not implemented as of Chrome unstable 59. 287 return configuration;
263 // See https://www.chromestatus.com/feature/5271355306016768 288
264 function protectConfiguration(configuration) 289 let iceServers = safeCopyArray(
265 { 290 configuration.iceServers,
266 if (configuration == null || typeof configuration != "object") 291 iceServer =>
267 return configuration; 292 {
268 293 let {url, urls} = iceServer;
269 let iceServers = safeCopyArray( 294
270 configuration.iceServers, 295 // RTCPeerConnection doesn't iterate through pseudo Arrays of urls.
271 iceServer => 296 if (typeof urls != "undefined" && !(urls instanceof RealArray))
272 { 297 urls = [urls];
273 let {url, urls} = iceServer; 298
274 299 return createObject(iceServer, {
275 // RTCPeerConnection doesn't iterate through pseudo Arrays of urls. 300 url: {
276 if (typeof urls != "undefined" && !(urls instanceof RealArray)) 301 configurable: false, enumerable: false, writable: false,
277 urls = [urls]; 302 value: normalizeUrl(url)
278 303 },
279 return createObject(iceServer, { 304 urls: {
280 url: { 305 configurable: false, enumerable: false, writable: false,
281 configurable: false, enumerable: false, writable: false, 306 value: safeCopyArray(urls, normalizeUrl)
282 value: normalizeUrl(url) 307 }
283 }, 308 });
284 urls: { 309 }
285 configurable: false, enumerable: false, writable: false, 310 );
286 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);
287 } 330 }
288 }); 331 catch (e) {}
289 } 332 }
290 ); 333 });
291 334 };
292 return createObject(configuration, { 335
293 iceServers: { 336 let checkConfiguration = (peerconnection, configuration) =>
294 configurable: false, enumerable: false, writable: false, 337 {
295 value: iceServers 338 if (configuration && configuration.iceServers)
296 } 339 {
297 }); 340 for (let i = 0; i < configuration.iceServers.length; i++)
298 }
299
300 function checkUrl(peerconnection, url)
301 {
302 checkRequest("webrtc", url, blocked =>
303 {
304 if (blocked)
305 {
306 // Calling .close() throws if already closed.
307 try
308 { 341 {
309 closeRTCPeerConnection(peerconnection); 342 let iceServer = configuration.iceServers[i];
310 } 343 if (iceServer)
311 catch (e) {}
312 }
313 });
314 }
315
316 function checkConfiguration(peerconnection, configuration)
317 {
318 if (configuration && configuration.iceServers)
319 {
320 for (let i = 0; i < configuration.iceServers.length; i++)
321 {
322 let iceServer = configuration.iceServers[i];
323 if (iceServer)
324 {
325 if (iceServer.url)
326 checkUrl(peerconnection, iceServer.url);
327
328 if (iceServer.urls)
329 { 344 {
330 for (let j = 0; j < iceServer.urls.length; j++) 345 if (iceServer.url)
331 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 }
332 } 353 }
333 } 354 }
334 } 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 };
335 } 376 }
336 } 377
337 378 let WrappedRTCPeerConnection = function(...args)
338 // Chrome unstable (tested with 59) has already implemented 379 {
339 // setConfiguration, so we need to wrap that if it exists too. 380 if (!(this instanceof WrappedRTCPeerConnection))
340 // https://www.chromestatus.com/feature/5596193748942848 381 return RealRTCPeerConnection();
341 if (RealRTCPeerConnection.prototype.setConfiguration) 382
342 { 383 let configuration = protectConfiguration(args[0]);
343 let realSetConfiguration = Function.prototype.call.bind( 384
344 RealRTCPeerConnection.prototype.setConfiguration 385 // Since the old webkitRTCPeerConnection constructor takes an optional
345 ); 386 // second argument we need to take care to pass that through. Necessary
346 387 // for older versions of Chrome such as 49.
347 RealRTCPeerConnection.prototype.setConfiguration = function(configuration) 388 let constraints = undefined;
348 { 389 if (args.length > 1)
349 configuration = protectConfiguration(configuration); 390 constraints = args[1];
350 391
351 // Call the real method first, so that validates the configuration for 392 let peerconnection = new RealRTCPeerConnection(configuration,
352 // us. Also we might as well since checkRequest is asynchronous anyway. 393 constraints);
353 realSetConfiguration(this, configuration); 394 checkConfiguration(peerconnection, configuration);
354 checkConfiguration(this, configuration); 395 return peerconnection;
355 }; 396 };
356 } 397
357 398 WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;
358 function WrappedRTCPeerConnection(...args) 399
359 { 400 let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
360 if (!(this instanceof WrappedRTCPeerConnection)) 401 copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
361 return RealRTCPeerConnection(); 402 ["generateCertificate", "name", "prototype"]);
362 403 RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;
363 let configuration = protectConfiguration(args[0]); 404
364 405 if ("RTCPeerConnection" in window)
365 // Since the old webkitRTCPeerConnection constructor takes an optional 406 window.RTCPeerConnection = boundWrappedRTCPeerConnection;
366 // second argument we need to take care to pass that through. Necessary 407 if ("webkitRTCPeerConnection" in window)
367 // for older versions of Chrome such as 49. 408 window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
368 let constraints = undefined; 409 }
369 if (args.length > 1)
370 constraints = args[1];
371
372 let peerconnection = new RealRTCPeerConnection(configuration, constraints);
373 checkConfiguration(peerconnection, configuration);
374 return peerconnection;
375 }
376
377 WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;
378
379 let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
380 copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
381 ["generateCertificate", "name", "prototype"]);
382 RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;
383
384 if ("RTCPeerConnection" in window)
385 window.RTCPeerConnection = boundWrappedRTCPeerConnection;
386 if ("webkitRTCPeerConnection" in window)
387 window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;
388 } 410 }
389 411
390 if (document instanceof HTMLDocument) 412 if (document instanceof HTMLDocument)
391 { 413 {
392 let sandbox = window.frameElement && 414 let sandbox = window.frameElement &&
393 window.frameElement.getAttribute("sandbox"); 415 window.frameElement.getAttribute("sandbox");
394 416
395 if (typeof sandbox != "string" || /(^|\s)allow-scripts(\s|$)/i.test(sandbox)) 417 if (typeof sandbox != "string" || /(^|\s)allow-scripts(\s|$)/i.test(sandbox))
396 { 418 {
397 let script = document.createElement("script"); 419 let script = document.createElement("script");
398 script.type = "application/javascript"; 420 script.type = "application/javascript";
399 script.async = false; 421 script.async = false;
400 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;
401 document.documentElement.appendChild(script); 427 document.documentElement.appendChild(script);
402 document.documentElement.removeChild(script); 428 document.documentElement.removeChild(script);
429 URL.revokeObjectURL(url);
403 } 430 }
404 } 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