commit f4f2caa26dd2d15e8f99d0a1357361da43e4fd2f Author: Arthur Edelstein arthuredelstein@gmail.com Date: Sat Apr 18 17:37:21 2015 -0700
Bug #15502, Part 2: Regression tests for blob URL isolation --- content/base/test/bug15502_page_blobify.html | 26 ++++++ content/base/test/bug15502_page_deblobify.html | 31 +++++++ content/base/test/bug15502_tab.html | 39 ++++++++ content/base/test/bug15502_utils.js | 104 ++++++++++++++++++++++ content/base/test/bug15502_worker_blobify.html | 28 ++++++ content/base/test/bug15502_worker_blobify.js | 12 +++ content/base/test/bug15502_worker_deblobify.html | 30 +++++++ content/base/test/bug15502_worker_deblobify.js | 24 +++++ content/base/test/mochitest.ini | 9 ++ content/base/test/test_tor_bug15502.html | 92 +++++++++++++++++++ 10 files changed, 395 insertions(+)
diff --git a/content/base/test/bug15502_page_blobify.html b/content/base/test/bug15502_page_blobify.html new file mode 100644 index 0000000..d883929 --- /dev/null +++ b/content/base/test/bug15502_page_blobify.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugs.torproject.org/15502 +--> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Page blobifier for Tor Browser Bug 15502</title> + <script type="text/javascript;version=1.7" src="bug15502_utils.js"></script> +</head> +<body> +<div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div> + +<script type="text/javascript;version=1.7"> + +Task.spawn(function* () { + sendMessage(window.parent, "ready"); + let message = yield receiveMessage(window.parent), + blobURL = stringToBlobURL(message); + sendMessage(window.parent, blobURL); + appendLine("display", message + " -> " + blobURL); +}); + +</script> +</body> +</html> diff --git a/content/base/test/bug15502_page_deblobify.html b/content/base/test/bug15502_page_deblobify.html new file mode 100644 index 0000000..e8cbd51 --- /dev/null +++ b/content/base/test/bug15502_page_deblobify.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugs.torproject.org/15502 +--> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Page deblobifier for Tor Browser Bug 15502</title> + <script type="text/javascript;version=1.7" src="bug15502_utils.js"></script> +</head> +<body> +<div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div> + +<script type="text/javascript;version=1.7"> + +Task.spawn(function* () { + sendMessage(window.parent, "ready"); + let blobURL = yield receiveMessage(window.parent), + string; + try { + string = yield blobURLtoString(blobURL); + } catch (e) { + string = e.message; + } + sendMessage(window.parent, string); + appendLine("display", blobURL + " -> " + string); +}); + +</script> +</body> +</html> diff --git a/content/base/test/bug15502_tab.html b/content/base/test/bug15502_tab.html new file mode 100644 index 0000000..7bd4744 --- /dev/null +++ b/content/base/test/bug15502_tab.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugs.torproject.org/15502 +--> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Tab for Tor Browser Bug 15502</title> + <script type="text/javascript;version=1.7" src="bug15502_utils.js"></script> +</head> +<body> + +<div id="display"></div> +<iframe id="child" width="100%"></iframe> + +<script type="text/javascript;version=1.7"> + +let iframe = document.getElementById("child"); + +let connect = function (sourceObject, destinationObject) { + Task.spawn(function* () { + for (;;) { + let message = yield receiveMessage(sourceObject); + sendMessage(destinationObject, message); + } + }); +}; + +Task.spawn(function* () { + sendMessage(window.opener, "ready"); + let firstParentMessage = yield receiveMessage(window.opener); + iframe.src = firstParentMessage; + connect(window.opener, iframe.contentWindow); + connect(iframe.contentWindow, window.opener); +}); +</script> + +</body> +</html> diff --git a/content/base/test/bug15502_utils.js b/content/base/test/bug15502_utils.js new file mode 100644 index 0000000..9d69c12 --- /dev/null +++ b/content/base/test/bug15502_utils.js @@ -0,0 +1,104 @@ +// Import Task.jsm +let { Task } = SpecialPowers.Cu.import("resource://gre/modules/Task.jsm"); + +// __listen(target, eventType, timeoutMs, useCapture)__. +// Calls addEventListener on target, with the given eventType. +// Returns a Promise that resolves to an Event object, if the event fires. +// If a timeout occurs, then Promise is rejected with a "Timed out" error. +// For use with Task.jsm. +let listen = function (target, eventType, timeoutMs, useCapture) { + return new Promise(function (resolve, reject) { + let listenFunction = function (event) { + target.removeEventListener(eventType, listenFunction, useCapture); + resolve(event); + }; + target.addEventListener(eventType, listenFunction, useCapture); + setTimeout(() => reject(new Error("Timed out")), timeoutMs); + }); +}; + +// __receiveMessage(source)__. +// Returns an event object for the next message received from source. +// A Task.jsm coroutine. +let receiveMessage = function* (source) { + let event; + do { + event = yield listen(self, "message", 5000, false); + } while (event.source !== source); + return event.data; +}; + +// __sendMessage(destination, message)__. +// Sends a message to destination. +let sendMessage = function (destination, message) { + destination.postMessage(message, "*"); +}; + +// __appendLine(id, lineString)__. +// Add a line of text to the innerHTML of element with id. +let appendLine = function (id, lineString) { + document.getElementById(id).innerHTML += lineString + "\n"; +}; + +// __xhr(method, url, responseType__. +// A simple async XMLHttpRequest call. +// Returns a promise with the response. +let xhr = function (method, url, responseType) { + return new Promise(function (resolve, reject) { + let xhr = new XMLHttpRequest(); + xhr.open(method, url, true); + xhr.onload = function () { + resolve(xhr.response); + }; + xhr.responseType = responseType; + xhr.send(); + }); +}; + +// __blobURLtoBlob(blobURL)__. +// Asynchronously retrieves a blob object +// from a blob URL. Returns a promise. +let blobURLtoBlob = function (blobURL) { + return xhr("GET", blobURL, "blob"); +}; + +// __blobToString(blob)__. +// Asynchronously reads the contents +// of a blob object into a string. Returns a promise. +let blobToString = function (blob) { + return new Promise(function (resolve, reject) { + let fileReader = new FileReader(); + fileReader.onload = function () { + resolve(fileReader.result); + }; + fileReader.readAsText(blob); + }); +}; + +// __blobURLtoString(blobURL)__. +// Asynchronous coroutine that takes a blobURL +// and returns the contents in a string. +let blobURLtoString = function* (blobURL) { + let blob = yield blobURLtoBlob(blobURL); + return yield blobToString(blob); +}; + +// __stringToBlobURL(s)__. +// Converts string s into a blob, and returns +// a blob URL. +let stringToBlobURL = function (s) { + let blob = new Blob([s]); + return URL.createObjectURL(blob); +}; + +// __workerIO(scriptFile, inputString)__. +// Sends inputString for the worker, and waits +// for the worker to return an outputString. +// Task.jsm coroutine. +let workerIO = function* (scriptFile, inputString) { + let worker = new Worker(scriptFile); + worker.postMessage(inputString); + let result = yield listen(worker, "message", 5000, false); + worker.terminate(); + return result.data; +}; diff --git a/content/base/test/bug15502_worker_blobify.html b/content/base/test/bug15502_worker_blobify.html new file mode 100644 index 0000000..3dd7926 --- /dev/null +++ b/content/base/test/bug15502_worker_blobify.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugs.torproject.org/15502 +--> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Worker blobifier for Tor Browser Bug 15502</title> + <script type="text/javascript;version=1.7" src="bug15502_utils.js"></script> +</head> +<body> +<div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div> + +<pre id="test"> +<script type="text/javascript;version=1.7"> + +Task.spawn(function* () { + sendMessage(window.parent, "ready"); + let message = yield receiveMessage(window.parent), + blobURL = yield workerIO("bug15502_worker_blobify.js", message); + sendMessage(window.parent, blobURL); + appendLine("display", message + " -> " + blobURL); +}); + +</script> +</pre> +</body> +</html> diff --git a/content/base/test/bug15502_worker_blobify.js b/content/base/test/bug15502_worker_blobify.js new file mode 100644 index 0000000..4aef347 --- /dev/null +++ b/content/base/test/bug15502_worker_blobify.js @@ -0,0 +1,12 @@ +// Wait for a string to be posted to this worker. +// Create a blob containing this string, and then +// post back a blob URL pointing to the blob. +self.addEventListener("message", function (e) { + try { + var blob = new Blob([e.data]), + blobURL = URL.createObjectURL(blob); + postMessage(blobURL); + } catch (e) { + postMessage(e.message); + } +}, false); diff --git a/content/base/test/bug15502_worker_deblobify.html b/content/base/test/bug15502_worker_deblobify.html new file mode 100644 index 0000000..30ec6b4 --- /dev/null +++ b/content/base/test/bug15502_worker_deblobify.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugs.torproject.org/15502 +--> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Worker deblobifier for Tor Browser Bug 15502</title> + <script type="text/javascript;version=1.7" src="bug15502_utils.js"></script> +</head> +<body> +<div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div> + +<pre id="test"> +<script type="text/javascript;version=1.7"> + +Task.spawn(function* () { + sendMessage(window.parent, "ready"); + let blobURL = yield receiveMessage(window.parent), + result = yield workerIO("bug15502_worker_deblobify.js", blobURL); + sendMessage(window.parent, result); + appendLine("display", blobURL + " -> " + result); +}); + + + +</script> +</pre> +</body> +</html> diff --git a/content/base/test/bug15502_worker_deblobify.js b/content/base/test/bug15502_worker_deblobify.js new file mode 100644 index 0000000..8556311 --- /dev/null +++ b/content/base/test/bug15502_worker_deblobify.js @@ -0,0 +1,24 @@ +// Wait for a blob URL to be posted to this worker. +// Obtain the blob, and read the string contained in it. +// Post back the string. + +var postStringInBlob = function (blobObject) { + var fileReader = new FileReaderSync(), + result = fileReader.readAsText(blobObject); + postMessage(result); +}; + +self.addEventListener("message", function (e) { + var blobURL = e.data, + xhr = new XMLHttpRequest(); + try { + xhr.open("GET", blobURL, true); + xhr.onload = function () { + postStringInBlob(xhr.response); + }; + xhr.responseType = "blob"; + xhr.send(); + } catch (e) { + postMessage(e.message); + } +}, false); diff --git a/content/base/test/mochitest.ini b/content/base/test/mochitest.ini index c3cffb3..e59a70a 100644 --- a/content/base/test/mochitest.ini +++ b/content/base/test/mochitest.ini @@ -12,6 +12,14 @@ support-files = badMessageEvent.eventsource^headers^ badMessageEvent2.eventsource badMessageEvent2.eventsource^headers^ + bug15502_page_blobify.html + bug15502_page_deblobify.html + bug15502_tab.html + bug15502_utils.js + bug15502_worker_blobify.js + bug15502_worker_blobify.html + bug15502_worker_deblobify.js + bug15502_worker_deblobify.html bug282547.sjs bug298064-subframe.html bug313646.txt @@ -604,6 +612,7 @@ skip-if = toolkit == 'android' || e10s #RANDOM [test_textnode_normalize_in_selection.html] [test_textnode_split_in_selection.html] [test_title.html] +[test_tor_bug15502.html] [test_treewalker_nextsibling.xml] [test_viewport_scroll.html] [test_viewsource_forbidden_in_object.html] diff --git a/content/base/test/test_tor_bug15502.html b/content/base/test/test_tor_bug15502.html new file mode 100644 index 0000000..9ed3d70 --- /dev/null +++ b/content/base/test/test_tor_bug15502.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugs.torproject.org/15502 +--> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Test for Tor Browser Bug 15502</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.7" src="bug15502_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content"></div> + +<script class="testbody" type="application/javascript;version=1.7"> +SimpleTest.waitForExplicitFinish(); + +// __prefs__. Import the `Serivces.prefs` object. +let prefs = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").Services.prefs; + +// ## Testing constants +let domain1 = "http://example.com", + domain2 = "http://example.net", + path = "/tests/content/base/test/", + page_blob = "bug15502_page_blobify.html", + page_deblob = "bug15502_page_deblobify.html" + worker_blob = "bug15502_worker_blobify.html", + worker_deblob = "bug15502_worker_deblobify.html"; + +// __tabIO(domain, child, input)__. +// Open a tab at the given `domain` and `child` page. Post an +// `input` message to the tab. +let tabIO = function* (domain, child, input) { + tab = window.open(domain + path + "bug15502_tab.html", "_blank"); + yield receiveMessage(tab); // ready message + sendMessage(tab, "http://example.org" + path + child); + yield receiveMessage(tab); // ready message + sendMessage(tab, input); + return yield receiveMessage(tab); +}; + +// __blobTest(isolationOn, domainA, domainB, blobPage, deblobPage)__. +// Run a test where we set the pref "privacy.thirdparty.isolate` to on or off, +// and then create a blob URL in `domainA`, using the page `blobPage`, +// and then attempt to retrieve the object from the blobURL in `domainB`, using +// the page `deblobPage`. +let blobTest = function* (isolationOn, domainA, domainB, blobPage, deblobPage) { + prefs.setIntPref("privacy.thirdparty.isolate", isolationOn ? 2 : 0); + let input = "" + Math.random(), + blobURL = yield tabIO(domainA, blobPage, input), + result = yield tabIO(domainB, deblobPage, blobURL), + description = domainA + ":" + blobPage + "->" + domainB + ":" + deblobPage + ", isolation " + (isolationOn ? "on." : "off."); + if (blobPage === worker_blob) { + // Remove this case when we write a patch that properly isolates web worker blob URLs + // by first party domain. + ok(blobURL.contains("Permission to call 'URL.createObjectURL' denied."), description + " Deny blob URL creation in web worker"); + } else if (deblobPage === worker_deblob && isolationOn) { + // Remove this case when we write a patch that properly isolates web worker blob URLs + // by first party domain. + ok(result.contains("Access to restricted URI denied"), description + " Isolated blobs not available to web workers"); + } else { + if (isolationOn && domainA !== domainB) { + ok(input !== result, description + " Deny retrieval"); + } else { + ok(input === result, description + " Allow retrieval"); + } + } +}; + + +// ## The main test +// Run a Task.jsm coroutine that tests various combinations of domains +// methods, and isolation states for reading and writing blob URLs. +Task.spawn(function* () { + for (let isolate of [false, true]) { + for (let domainB of [domain1, domain2]) { + for (let blob of [page_blob, worker_blob]) { + for (let deblob of [page_deblob, worker_deblob]) { + yield blobTest(isolate, domain1, domainB, blob, deblob); + } + } + } + } + SimpleTest.finish(); +}); + +</script> + +</body> +</html>