Changeset 199221 in webkit


Ignore:
Timestamp:
Apr 8, 2016 12:17:50 AM (8 years ago)
Author:
commit-queue@webkit.org
Message:

CSP: Block XHR when calling XMLHttpRequest.send() and throw network error.
https://bugs.webkit.org/show_bug.cgi?id=153598
<rdar://problem/24391483>

Patch by John Wilander <wilander@apple.com> on 2016-04-08
Reviewed by Darin Adler.

Source/WebCore:

No new tests. Changes to existing tests are sufficient.

  • xml/XMLHttpRequest.cpp:

(WebCore::XMLHttpRequest::open):
(WebCore::XMLHttpRequest::initSend):

Moved the CSP check from XMLHttpRequest::open() to XMLHttpRequest::initSend().
Changed the thrown error type from Security to Network for synchronous requests.
Changed from throwing an error to firing an error event for asynchronous requests.
These changes are in conformance with connect-src of Content Security Policy Level 2.
https://www.w3.org/TR/CSP2/#directive-connect-src (W3C Candidate Recommendation, 21 July 2015)

LayoutTests:

  • fast/workers/resources/worker-inherits-csp-blocks-xhr.js:

(catch):

  • fast/workers/worker-inherits-csp-blocks-xhr-expected.txt:

Changed expected error from DOMException.SECURITY_ERR to DOMException.NETWORK_ERR.

  • http/tests/security/contentSecurityPolicy/connect-src-xmlhttprequest-blocked-expected.txt:
  • http/tests/security/contentSecurityPolicy/connect-src-xmlhttprequest-blocked.html:

Now tests that XMLHttpRequest.send() is blocked if the URL voilates the connect-src directive in CSP.

  • http/tests/security/contentSecurityPolicy/resources/worker.php:

Added two additional calls to XMLHttpRequest.send() and switched to receiving an error event to make
existing tests work with code changes.

  • http/tests/security/contentSecurityPolicy/source-list-parsing-malformed-meta.html:

Added an additional call to XMLHttpRequest.send() and switched to receiving an error event to make
existing test work with code changes.

  • http/tests/security/isolatedWorld/bypass-main-world-csp-for-xhr-expected.txt:
  • http/tests/security/isolatedWorld/bypass-main-world-csp-for-xhr.html:

Added an additional call to XMLHttpRequest.send() and switched to receiving an error event to make
existing tests work with code changes.
Refactored test mechnism with additional parameters to cover synchronous/asynchronous as well as
same-origin/cross-origin in isolated worlds.

Location:
trunk
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r199216 r199221  
     12016-04-08  John Wilander  <wilander@apple.com>
     2
     3        CSP: Block XHR when calling XMLHttpRequest.send() and throw network error.
     4        https://bugs.webkit.org/show_bug.cgi?id=153598
     5        <rdar://problem/24391483>
     6
     7        Reviewed by Darin Adler.
     8
     9        * fast/workers/resources/worker-inherits-csp-blocks-xhr.js:
     10        (catch):
     11        * fast/workers/worker-inherits-csp-blocks-xhr-expected.txt:
     12            Changed expected error from DOMException.SECURITY_ERR to DOMException.NETWORK_ERR.
     13        * http/tests/security/contentSecurityPolicy/connect-src-xmlhttprequest-blocked-expected.txt:
     14        * http/tests/security/contentSecurityPolicy/connect-src-xmlhttprequest-blocked.html:
     15            Now tests that XMLHttpRequest.send() is blocked if the URL voilates the connect-src directive in CSP.
     16        * http/tests/security/contentSecurityPolicy/resources/worker.php:
     17            Added two additional calls to XMLHttpRequest.send() and switched to receiving an error event to make
     18            existing tests work with code changes.
     19        * http/tests/security/contentSecurityPolicy/source-list-parsing-malformed-meta.html:
     20            Added an additional call to XMLHttpRequest.send() and switched to receiving an error event to make
     21            existing test work with code changes.
     22        * http/tests/security/isolatedWorld/bypass-main-world-csp-for-xhr-expected.txt:
     23        * http/tests/security/isolatedWorld/bypass-main-world-csp-for-xhr.html:
     24            Added an additional call to XMLHttpRequest.send() and switched to receiving an error event to make
     25            existing tests work with code changes.
     26            Refactored test mechnism with additional parameters to cover synchronous/asynchronous as well as
     27            same-origin/cross-origin in isolated worlds.
     28
    1292016-04-07  Darin Adler  <darin@apple.com>
    230
  • trunk/LayoutTests/fast/workers/resources/worker-inherits-csp-blocks-xhr.js

    r195948 r199221  
    88    exception = e;
    99}
    10 // FIXME: We should be throwing a DOMException.NETWORK_ERR. See <https://bugs.webkit.org/show_bug.cgi?id=153598>.
    11 var expectedExceptionCode = 18; // DOMException.SECURITY_ERR
     10
     11var expectedExceptionCode = 19; // DOMException.NETWORK_ERR
    1212if (!exception)
    1313    self.postMessage("FAIL should throw " + expectedExceptionCode + ". But did not throw an exception.");
  • trunk/LayoutTests/fast/workers/worker-inherits-csp-blocks-xhr-expected.txt

    r198591 r199221  
    22This tests that the Content Security Policy (CSP) of the owner document (this page) blocks a file-URL Web Worker from making an XHR request because the parent's CSP contains "connect-src 'none'"
    33
    4 PASS threw exception Error: SecurityError: DOM Exception 18.
     4PASS threw exception Error: NetworkError: DOM Exception 19.
  • trunk/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-xmlhttprequest-blocked-expected.txt

    r198591 r199221  
    11CONSOLE MESSAGE: Refused to connect to http://localhost:8000/xmlhttprequest/resources/get.txt because it does not appear in the connect-src directive of the Content Security Policy.
    2 Pass
     2CONSOLE MESSAGE: Refused to connect to http://localhost:8000/xmlhttprequest/resources/get.txt because it does not appear in the connect-src directive of the Content Security Policy.
     3CONSOLE MESSAGE: Refused to connect to http://localhost:8000/xmlhttprequest/resources/get.txt because it does not appear in the connect-src directive of the Content Security Policy.
     4This tests that a Content Security Policy violation for an XHR is triggered when calling XMLHttpRequest.send().
    35
     6On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
     7
     8
     9PASS xhrSync.open("GET", "http://localhost:8000/xmlhttprequest/resources/get.txt", false) did not throw exception.
     10PASS xhrSync.send() threw exception Error: NetworkError: DOM Exception 19.
     11PASS xhrAsync.open("GET", "http://localhost:8000/xmlhttprequest/resources/get.txt", true) did not throw exception.
     12PASS xhrAsync.send() did not throw exception.
     13PASS xhrAsyncAbort.open("GET", "http://localhost:8000/xmlhttprequest/resources/get.txt", true) did not throw exception.
     14PASS xhrAsyncAbort.send();xhrAsyncAbort.abort();  did not throw exception.
     15PASS An error event was received for the asynchronous call.
     16PASS An error event was received for the aborted asynchronous call.
     17PASS successfullyParsed is true
     18
     19TEST COMPLETE
     20
  • trunk/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-xmlhttprequest-blocked.html

    r133095 r199221  
    33<head>
    44<meta http-equiv="Content-Security-Policy" content="connect-src http://127.0.0.1:8000">
    5 <script>
    6 if (window.testRunner)
    7     testRunner.dumpAsText();
    8 </script>
     5<script src="../../../resources/js-test-pre.js"></script>
    96</head>
    107<body>
    11 <pre id="console"></pre>
    128<script>
    13 function log(msg)
    14 {
    15     document.getElementById("console").appendChild(document.createTextNode(msg + "\n"));
    16 }
     9description("This tests that a Content Security Policy violation for an XHR is triggered when calling XMLHttpRequest.send().");
    1710
    18 try {
    19     var xhr = new XMLHttpRequest;
    20     xhr.open("GET", "http://localhost:8000/xmlhttprequest/resources/get.txt", true);
    21     log("Fail");
    22 } catch(e) {
    23     log("Pass");
    24 }
     11jsTestIsAsync = true;
     12
     13var xhrSync = new XMLHttpRequest;
     14xhrSync.addEventListener("error", function () {
     15    debug("FAIL An error event should not have been received.");
     16});
     17
     18var xhrAsync = new XMLHttpRequest;
     19xhrAsync.addEventListener("error", function () {
     20    debug("PASS An error event was received for the asynchronous call.");
     21});
     22
     23var xhrAsyncAbort = new XMLHttpRequest;
     24xhrAsyncAbort.addEventListener("error", function () {
     25    debug("PASS An error event was received for the aborted asynchronous call.");
     26    finishJSTest();
     27});
     28
     29shouldNotThrow('xhrSync.open("GET", "http://localhost:8000/xmlhttprequest/resources/get.txt", false)'); // Synchronous request
     30shouldThrow("xhrSync.send()", "'Error: NetworkError: DOM Exception 19'");
     31
     32shouldNotThrow('xhrAsync.open("GET", "http://localhost:8000/xmlhttprequest/resources/get.txt", true)'); // Asynchronous request
     33shouldNotThrow("xhrAsync.send()");
     34
     35shouldNotThrow('xhrAsyncAbort.open("GET", "http://localhost:8000/xmlhttprequest/resources/get.txt", true)'); // Asynchronous request
     36shouldNotThrow("xhrAsyncAbort.send();xhrAsyncAbort.abort(); ");
    2537
    2638</script>
     39<script src="/js-test-resources/js-test-post.js"></script>
    2740</body>
    2841</html>
  • trunk/LayoutTests/http/tests/security/contentSecurityPolicy/resources/worker.php

    r195367 r199221  
    5959?>
    6060
    61 try {
    62     var xhr = new XMLHttpRequest;
    63     xhr.open("GET", "http://127.0.0.1:8000/xmlhttprequest/resources/get.txt", true);
     61var xhr = new XMLHttpRequest;
     62xhr.addEventListener("load", function () {
    6463    postMessage("xhr allowed");
    65 } catch(e) {
     64});
     65xhr.addEventListener("error", function () {
    6666    postMessage("xhr blocked");
    67 }
     67});
     68xhr.open("GET", "http://127.0.0.1:8000/xmlhttprequest/resources/get.txt", true);
     69xhr.send();
    6870
    6971<?php
     
    116118?>
    117119
    118 try {
    119     var xhr = new XMLHttpRequest;
    120     xhr.open("GET", "http://127.0.0.1:8000/xmlhttprequest/resources/get.txt", true);
     120var xhr = new XMLHttpRequest;
     121xhr.addEventListener("load", function () {
    121122    postMessage("xhr allowed");
    122 } catch(e) {
     123});
     124xhr.addEventListener("error", function () {
    123125    postMessage("xhr blocked");
    124 }
     126});
     127xhr.open("GET", "http://127.0.0.1:8000/xmlhttprequest/resources/get.txt", true);
     128xhr.send();
    125129
    126130var id = 0;
  • trunk/LayoutTests/http/tests/security/contentSecurityPolicy/source-list-parsing-malformed-meta.html

    r133095 r199221  
    1818try {
    1919    var xhr = new XMLHttpRequest;
    20     xhr.open("GET", "http://127.0.0.1:8000/xmlhttprequest/resources/get.txt", true);
     20    xhr.open("GET", "http://127.0.0.1:8000/xmlhttprequest/resources/get.txt", false);
     21    xhr.send();
    2122    log("Fail");
    2223} catch(e) {
  • trunk/LayoutTests/http/tests/security/isolatedWorld/bypass-main-world-csp-for-xhr-expected.txt

    r198591 r199221  
    1 CONSOLE MESSAGE: Refused to connect to http://localhost:8000/security/isolatedWorld/resources/cross-origin-xhr.txt because it does not appear in the connect-src directive of the Content Security Policy.
     1CONSOLE MESSAGE: Refused to connect to http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow.cgi because it does not appear in the connect-src directive of the Content Security Policy.
     2CONSOLE MESSAGE: Refused to connect to http://127.0.0.1:8000/xmlhttprequest/resources/access-control-basic-allow.cgi because it does not appear in the connect-src directive of the Content Security Policy.
    23Tests that isolated worlds can have XHRs that the page's CSP wouldn't allow.
    34
     
    56
    67
    7 XHR from main world
    8 PASS: XHR.open threw an exception.
    9 XHR from isolated world
    10 PASS: XHR.open did not throw an exception.
     8Synchronous XHR same-origin from main world
     9PASS: XHR.send threw an exception.
     10Asynchronous XHR same-origin from main world
     11PASS: XHR.send did not throw an exception.
     12PASS: XHR.send received an error event.
     13Synchronous XHR same-origin from isolated world
     14PASS: XHR.send did not throw an exception.
     15Asynchronous XHR same-origin from isolated world
     16PASS: XHR.send did not throw an exception.
     17PASS: XHR.send received a load event.
     18Synchronous XHR cross-origin from isolated world
     19PASS: XHR.send did not throw an exception.
     20Asynchronous XHR cross-origin from isolated world
     21PASS: XHR.send did not throw an exception.
     22PASS: XHR.send received a load event.
    1123PASS successfullyParsed is true
    1224
  • trunk/LayoutTests/http/tests/security/isolatedWorld/bypass-main-world-csp-for-xhr.html

    r148076 r199221  
    11<!DOCTYPE html>
    22<html>
     3<head>
    34    <script src="../../js-test-resources/js-test-pre.js"></script>
    45    <meta http-equiv="Content-Security-Policy" content="connect-src 'none'">
     6</head>
    57<body>
    68<p id="description"></p>
     
    1214jsTestIsAsync = true;
    1315
     16const SameOrigin = true;
     17const CrossOrigin = false;
     18const Asynchronous = true;
     19const Synchronous = false;
     20const ShouldBlock = true;
     21const ShouldNotBlock = false;
     22
    1423var tests = [
    1524    function() {
    16         debug('XHR from main world');
    17         xhr(true);
     25        debug('Synchronous XHR same-origin from main world');
     26        xhr({sameOrigin : true}, Synchronous, ShouldBlock);
    1827    },
    1928    function() {
    20         debug('XHR from isolated world');
    21         runTestInWorld(1, 'xhr', 'false');
     29        debug('Asynchronous XHR same-origin from main world');
     30        xhr({sameOrigin : true}, Asynchronous, ShouldBlock);
    2231    },
     32    function() {
     33        debug('Synchronous XHR same-origin from isolated world');
     34        invokeInWorld(1, xhr, SameOrigin, Synchronous, ShouldNotBlock);
     35    },
     36    function() {
     37        debug('Asynchronous XHR same-origin from isolated world');
     38        invokeInWorld(2, xhr, SameOrigin, Asynchronous, ShouldNotBlock);
     39    },
     40    function() {
     41        debug('Synchronous XHR cross-origin from isolated world');
     42        invokeInWorld(3, xhr, CrossOrigin, Synchronous, ShouldNotBlock);
     43    },
     44    function() {
     45        debug('Asynchronous XHR cross-origin from isolated world');
     46        invokeInWorld(4, xhr, CrossOrigin, Asynchronous, ShouldNotBlock);
     47    }
    2348];
    2449var currentTest = 0;
     
    5378}
    5479
    55 function runTestInWorld(worldId, funcName, param)
    56 {
    57     testRunner.evaluateScriptInIsolatedWorld(
    58         worldId, String(eval(funcName)) + "\n" + funcName + "(" + param + ");");
     80// This function will only successfully pass on JSON-stringifieable arguments such as numbers and strings to aNamedFunction
     81function invokeInWorld(worldId, aNamedFunction) {
     82    console.assert(aNamedFunction.name);
     83    var argumentsToPass = Array.prototype.slice.call(arguments, 2);
     84    var script = aNamedFunction.toString() + '; ' + aNamedFunction.name + '(' + argumentsToPass.map(JSON.stringify).join(', ') + ');';
     85    testRunner.evaluateScriptInIsolatedWorld(worldId, script);
    5986}
    6087
    61 function xhr(shouldBlock)
    62 {
     88function xhr(isSameOrigin, isAsync, shouldBlock) {
    6389    function debug(message) {
    6490        window.postMessage(JSON.stringify({
     
    6995    }
    7096
     97    var url = (isSameOrigin ? 'http://127.0.0.1:8000/' : 'http://localhost:8000/') + 'xmlhttprequest/resources/access-control-basic-allow.cgi';
    7198    var xhr = new XMLHttpRequest();
     99    var asyncCallDone = false;
     100    var finallyClauseDone = false;
     101
     102    xhr.open('GET', url, isAsync);
     103
     104    if (isAsync) {
     105        xhr.addEventListener('load', function() {
     106            if (shouldBlock)
     107                debug('FAIL: XHR.send should not have received a load event.');
     108            else
     109                debug('PASS: XHR.send received a load event.');
     110
     111            if (finallyClauseDone)
     112                window.postMessage(JSON.stringify({'type': 'test-done'}), '*');
     113            else
     114                asyncCallDone = true;
     115        });
     116
     117        xhr.addEventListener('error', function() {
     118            if (shouldBlock)
     119                debug('PASS: XHR.send received an error event.');
     120            else
     121                debug('FAIL: XHR.send should not have received an error event.');
     122
     123            if (finallyClauseDone)
     124                window.postMessage(JSON.stringify({'type': 'test-done'}), '*');
     125            else
     126                asyncCallDone = true;
     127        });
     128    }
     129
    72130    try {
    73         xhr.open('GET', 'http://localhost:8000/security/isolatedWorld/resources/cross-origin-xhr.txt', true);
    74         if (shouldBlock)
    75             debug('FAIL: XHR.open should have thrown an exception.');
     131        xhr.send();
     132        if (shouldBlock && !isAsync)
     133            debug('FAIL: XHR.send should have thrown an exception.');
    76134        else
    77             debug('PASS: XHR.open did not throw an exception.');
     135            debug('PASS: XHR.send did not throw an exception.');
    78136    } catch (e) {
    79         if (shouldBlock)
    80             debug('PASS: XHR.open threw an exception.');
     137        if (shouldBlock && !isAsync)
     138            debug('PASS: XHR.send threw an exception.');
    81139        else
    82             debug('FAIL: XHR.open should not have thrown an exception.');
     140            debug('FAIL: XHR.send should not have thrown an exception.');
    83141    } finally {
    84         window.postMessage(JSON.stringify({'type': 'test-done'}), '*');
     142        if (!isAsync || asyncCallDone)
     143            window.postMessage(JSON.stringify({'type': 'test-done'}), '*');
     144        else
     145            finallyClauseDone = true;
    85146    }
    86147}
    87 
    88148</script>
    89 
    90149<script src="../../js-test-resources/js-test-post.js"></script>
    91150</body>
  • trunk/Source/WebCore/ChangeLog

    r199216 r199221  
     12016-04-08  John Wilander  <wilander@apple.com>
     2
     3        CSP: Block XHR when calling XMLHttpRequest.send() and throw network error.
     4        https://bugs.webkit.org/show_bug.cgi?id=153598
     5        <rdar://problem/24391483>
     6
     7        Reviewed by Darin Adler.
     8
     9        No new tests. Changes to existing tests are sufficient.
     10
     11        * xml/XMLHttpRequest.cpp:
     12        (WebCore::XMLHttpRequest::open):
     13        (WebCore::XMLHttpRequest::initSend):
     14            Moved the CSP check from XMLHttpRequest::open() to XMLHttpRequest::initSend().
     15            Changed the thrown error type from Security to Network for synchronous requests.
     16            Changed from throwing an error to firing an error event for asynchronous requests.
     17            These changes are in conformance with connect-src of Content Security Policy Level 2.
     18            https://www.w3.org/TR/CSP2/#directive-connect-src (W3C Candidate Recommendation, 21 July 2015)
     19
    1202016-04-07  Darin Adler  <darin@apple.com>
    221
  • trunk/Source/WebCore/xml/XMLHttpRequest.cpp

    r198869 r199221  
    498498    }
    499499
    500     // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
    501     if (!scriptExecutionContext()->contentSecurityPolicy()->allowConnectToSource(url, scriptExecutionContext()->shouldBypassMainWorldContentSecurityPolicy())) {
    502         // FIXME: Should this be throwing an exception?
    503         ec = SECURITY_ERR;
    504         return;
    505     }
    506 
    507500    if (!async && scriptExecutionContext()->isDocument()) {
    508501        if (document()->settings() && !document()->settings()->syncXHRInDocumentsEnabled()) {
     
    573566    }
    574567    ASSERT(!m_loader);
     568
     569    // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
     570    if (!scriptExecutionContext()->contentSecurityPolicy()->allowConnectToSource(m_url, scriptExecutionContext()->shouldBypassMainWorldContentSecurityPolicy())) {
     571        if (m_async) {
     572            setPendingActivity(this);
     573            m_timeoutTimer.stop();
     574            m_networkErrorTimer.startOneShot(0);
     575        } else
     576            ec = NETWORK_ERR;
     577        return false;
     578    }
    575579
    576580    m_error = false;
Note: See TracChangeset for help on using the changeset viewer.