Bug 1073952: tests for iframe sandbox srcdoc and data URIs with CSP r=ckerschb,Tomcat
authorFrederik Braun <fbraun+gh@mozilla.com>
Mon, 30 Jan 2017 14:12:15 +0100
changeset 348406 461141e9b3c92aae1a79bc5e492ec49629cc81a2
parent 348405 37d0954bf1431c13ef29457a124c691201674068
child 348407 bf127c7f5a9ba62c0d86b63421c73cdc7d066893
push id39150
push usercbook@mozilla.com
push dateMon, 20 Mar 2017 11:22:53 +0000
treeherderautoland@bf127c7f5a9b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, Tomcat
bugs1073952
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1073952: tests for iframe sandbox srcdoc and data URIs with CSP r=ckerschb,Tomcat MozReview-Commit-ID: 5Q8XIJPrRPk
dom/security/test/csp/file_iframe_sandbox_srcdoc.html
dom/security/test/csp/file_iframe_sandbox_srcdoc.html^headers^
dom/security/test/csp/file_iframe_srcdoc.sjs
dom/security/test/csp/mochitest.ini
dom/security/test/csp/test_iframe_sandbox_srcdoc.html
dom/security/test/csp/test_iframe_srcdoc.html
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_sandbox_srcdoc.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1073952 - CSP should restrict scripts in srcdoc iframe even if sandboxed</title>
+</head>
+<body>
+<iframe srcdoc="<img src=x onerror='parent.postMessage({result: `unexpected-csp-violation`}, `*`);'>"
+        sandbox="allow-scripts"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_sandbox_srcdoc.html^headers^
@@ -0,0 +1,1 @@
+content-security-policy: default-src *;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_srcdoc.sjs
@@ -0,0 +1,79 @@
+// Custom *.sjs file specifically for the needs of
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1073952
+
+"use strict";
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+const SCRIPT = `
+  <script>
+    parent.parent.postMessage({result: &quot;allowed&quot;}, &quot;*&quot;);
+  </script>`;
+
+const SIMPLE_IFRAME_SRCDOC = `
+  <!DOCTYPE html>
+  <html>
+  <head><meta charset="utf-8"></head>
+  <body>
+    <iframe sandbox="allow-scripts" srcdoc="` + SCRIPT + `"></iframe>
+  </body>
+  </html>`;
+
+const INNER_SRCDOC_IFRAME = `
+  <iframe sandbox='allow-scripts' srcdoc='<script>
+      parent.parent.parent.postMessage({result: &quot;allowed&quot;}, &quot;*&quot;);
+    </script>'>
+  </iframe>`;
+
+const NESTED_IFRAME_SRCDOC = `
+  <!DOCTYPE html>
+  <html>
+  <head><meta charset="utf-8"></head>
+  <body>
+    <iframe sandbox="allow-scripts" srcdoc="` + INNER_SRCDOC_IFRAME + `"></iframe>
+  </body>
+  </html>`;
+
+
+const INNER_DATAURI_IFRAME = `
+  <iframe sandbox='allow-scripts' src='data:text/html,<script>
+      parent.parent.parent.postMessage({result: &quot;allowed&quot;}, &quot;*&quot;);
+    </script>'>
+  </iframe>`;
+
+const NESTED_IFRAME_SRCDOC_DATAURI = `
+  <!DOCTYPE html>
+  <html>
+  <head><meta charset="utf-8"></head>
+  <body>
+    <iframe sandbox="allow-scripts" srcdoc="` + INNER_DATAURI_IFRAME + `"></iframe>
+  </body>
+  </html>`;
+
+function handleRequest(request, response) {
+  const query = new URLSearchParams(request.queryString);
+
+  response.setHeader("Cache-Control", "no-cache", false);
+  if (typeof query.get("csp") === "string") {
+    response.setHeader("Content-Security-Policy", query.get("csp"), false);
+  }
+  response.setHeader("Content-Type", "text/html", false);
+
+  if (query.get("action") === "simple_iframe_srcdoc") {
+    response.write(SIMPLE_IFRAME_SRCDOC);
+    return;
+  }
+
+  if (query.get("action") === "nested_iframe_srcdoc") {
+    response.write(NESTED_IFRAME_SRCDOC);
+    return;
+  }
+
+  if (query.get("action") === "nested_iframe_srcdoc_datauri") {
+    response.write(NESTED_IFRAME_SRCDOC_DATAURI);
+    return;
+  }
+
+  // we should never get here, but just in case
+  // return something unexpected
+  response.write("do'h");
+}
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -200,16 +200,19 @@ support-files =
   file_strict_dynamic_non_parser_inserted.html
   file_strict_dynamic_non_parser_inserted_inline.html
   file_strict_dynamic_unsafe_eval.html
   file_strict_dynamic_default_src.html
   file_strict_dynamic_default_src.js
   file_upgrade_insecure_navigation.sjs
   file_punycode_host_src.sjs
   file_punycode_host_src.js
+  file_iframe_srcdoc.sjs
+  file_iframe_sandbox_srcdoc.html
+  file_iframe_sandbox_srcdoc.html^headers^
 
 [test_base-uri.html]
 [test_blob_data_schemes.html]
 [test_connect-src.html]
 [test_CSP.html]
 [test_allow_https_schemes.html]
 [test_bug663567.html]
 [test_bug802872.html]
@@ -288,8 +291,10 @@ tags = mcb
 [test_upgrade_insecure_docwrite_iframe.html]
 [test_bug1242019.html]
 [test_bug1312272.html]
 [test_strict_dynamic.html]
 [test_strict_dynamic_parser_inserted.html]
 [test_strict_dynamic_default_src.html]
 [test_upgrade_insecure_navigation.html]
 [test_punycode_host_src.html]
+[test_iframe_sandbox_srcdoc.html]
+[test_iframe_srcdoc.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox_srcdoc.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1073952 - CSP should restrict scripts in srcdoc iframe even if sandboxed</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">Bug 1073952</p>
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+  SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+
+examiner.prototype  = {
+  observe: function(subject, topic, data) {
+
+    if(topic === "csp-on-violate-policy") {
+      var violationString = SpecialPowers.getPrivilegedProps(SpecialPowers.
+                             do_QueryInterface(subject, "nsISupportsCString"), "data");
+      // the violation subject for inline script violations is unfortunately vague,
+      // all we can do is match the string.
+      if (!violationString.includes("Inline Script")) {
+        return
+      }
+      ok(true, "CSP inherited into sandboxed srcdoc iframe, script blocked.");
+      window.testFinished();
+    }
+  },
+
+  // must eventually call this to remove the listener,
+  // or mochitests might get borked.
+  remove: function() {
+    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+  }
+}
+
+window.examiner = new examiner();
+
+function testFinished() {
+  window.examiner.remove();
+  SimpleTest.finish();
+}
+
+addEventListener("message", function(e) {
+  ok(false, "We should not execute JS in srcdoc iframe.");
+  window.testFinished();
+})
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_iframe_sandbox_srcdoc.html';
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_srcdoc.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1073952 - Test CSP enforcement within iframe srcdoc</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * (1) We serve a site which makes use of script-allowed sandboxed iframe srcdoc
+ *     and make sure that CSP applies to the nested browsing context
+ *     within the iframe.
+ *     [PAGE WITH CSP [IFRAME SANDBOX SRCDOC [SCRIPT]]]
+ *
+ * (2) We serve a site which nests script within an script-allowed sandboxed
+ *     iframe srcdoc within another script-allowed sandboxed iframe srcdoc and
+ *     make sure that CSP applies to the nested browsing context
+ *     within the iframe*s*.
+ *     [PAGE WITH CSP [IFRAME SANDBOX SRCDOC [IFRAME SANDBOX SRCDOC [SCRIPT]]]]
+ *
+ * Please note that the test relies on the "csp-on-violate-policy" observer.
+ * Whenever the script within the iframe is blocked observers are notified.
+ * In turn, this renders the 'result' within tests[] unused. In case the script
+ * would execute however, the postMessageHandler would bubble up 'allowed' and
+ * the test would fail.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+  // [PAGE *WITHOUT* CSP [IFRAME SRCDOC [SCRIPT]]]
+  { csp: "",
+    result: "allowed",
+    query: "simple_iframe_srcdoc",
+    desc: "No CSP should run script within script-allowed sandboxed iframe srcdoc"
+  },
+  { csp: "script-src https://test1.com",
+    result: "blocked",
+    query: "simple_iframe_srcdoc",
+    desc: "CSP should block script within script-allowed sandboxediframe srcdoc"
+  },
+  // [PAGE *WITHOUT* CSP [IFRAME SRCDOC [IFRAME SRCDOC [SCRIPT]]]]
+  { csp: "",
+    result: "allowed",
+    query: "nested_iframe_srcdoc",
+    desc: "No CSP should run script within script-allowed sandboxed iframe srcdoc nested within another script-allowed sandboxed iframe srcdoc"
+  },
+  // [PAGE WITH CSP [IFRAME SRCDOC ]]
+  { csp: "script-src https://test2.com",
+    result: "blocked",
+    query: "nested_iframe_srcdoc",
+    desc: "CSP should block script within script-allowed sandboxed iframe srcdoc nested within another script-allowed sandboxed iframe srcdoc"
+  },
+  { csp: "",
+    result: "allowed",
+    query: "nested_iframe_srcdoc_datauri",
+    desc: "No CSP, should run script within script-allowed sandboxed iframe src with data URL nested within another script-allowed sandboxed iframe srcdoc"
+  },
+  { csp: "script-src https://test3.com",
+    result: "blocked",
+    query: "nested_iframe_srcdoc_datauri",
+    desc: "CSP should block script within script-allowed sandboxed iframe src with data URL nested within another script-allowed sandboxed iframe srcdoc"
+  },
+
+];
+
+// initializing to -1 so we start at index 0 when we start the test
+var counter = -1;
+
+function finishTest() {
+  window.removeEventListener("message", receiveMessage, false);
+  window.examiner.remove();
+  SimpleTest.finish();
+}
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+  var result = event.data.result;
+  testComplete(result, tests[counter].result, tests[counter].desc);
+}
+
+function examiner() {
+  SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+
+examiner.prototype  = {
+  observe: function(subject, topic, data) {
+    if (topic === "csp-on-violate-policy") {
+      var violationString = SpecialPowers.getPrivilegedProps(SpecialPowers.
+                             do_QueryInterface(subject, "nsISupportsCString"), "data");
+      // the violation subject for inline script violations is unfortunately vague,
+      // all we can do is match the string.
+      if (!violationString.includes("Inline Script")) {
+        return
+      }
+      testComplete("blocked", tests[counter].result, tests[counter].desc);
+    }
+  },
+  remove: function() {
+    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+  }
+}
+
+function testComplete(result, expected, desc) {
+  is(result, expected, desc);
+  // ignore cases when we get csp violations and postMessage from  the same frame.
+  var frameURL = new URL(document.getElementById("testframe").src);
+  var params = new URLSearchParams(frameURL.search);
+  var counterInFrame = params.get("counter");
+  if (counterInFrame == counter) {
+    loadNextTest();
+  }
+}
+
+function loadNextTest() {
+  counter++;
+  if (counter == tests.length) {
+    finishTest();
+    return;
+  }
+  var src = "file_iframe_srcdoc.sjs";
+  src += "?csp=" + escape(tests[counter].csp);
+  src += "&action=" + escape(tests[counter].query);
+  src += "&counter=" + counter;
+  document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+window.examiner = new examiner();
+loadNextTest();
+
+</script>
+</body>
+</html>