Changeset 121883 in webkit


Ignore:
Timestamp:
Jul 4, 2012 11:45:37 PM (12 years ago)
Author:
commit-queue@webkit.org
Message:

Implement the script-nonce Content Security Policy directive.
https://bugs.webkit.org/show_bug.cgi?id=89577

Patch by Mike West <mkwst@chromium.org> on 2012-07-04
Reviewed by Adam Barth.

Source/WebCore:

This patch implements the (experimental) script-nonce Content Security
Policy directive from the 1.1 spec, which allows for selective
execution of script by specifying a "nonce" attribute for the
script tag. Script is only loaded and executed if it both matches the
nonce and matches the script-src whitelist (if present).

The implementation is gated on the ENABLE_CSP_NEXT flag, which is
currently disabled for all ports other than Chromium.

Spec: https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#script-nonce--experimental

Tests: http/tests/security/contentSecurityPolicy/1.1/scriptnonce-allowed.html

http/tests/security/contentSecurityPolicy/1.1/scriptnonce-badnonce.html
http/tests/security/contentSecurityPolicy/1.1/scriptnonce-blocked.html
http/tests/security/contentSecurityPolicy/1.1/scriptnonce-emptynonce.html
http/tests/security/contentSecurityPolicy/1.1/scriptnonce-scriptsrc-blocked.html

  • dom/ScriptElement.cpp:

(WebCore::ScriptElement::requestScript):
(WebCore::ScriptElement::executeScript):

Passing the nonce attribute through to check against CSP.

  • html/HTMLAttributeNames.in:
  • html/HTMLScriptElement.idl:

Adding the nonce attribute to the script tag.

  • page/ContentSecurityPolicy.cpp:

(CSPDirectiveList):
(WebCore::CSPDirectiveList::logInvalidNonce):
(WebCore):
(WebCore::CSPDirectiveList::checkNonceAndReportViolation):
(WebCore::CSPDirectiveList::allowJavaScriptURLs):
(WebCore::CSPDirectiveList::allowInlineEventHandlers):

If a nonce is set, deny JavaScript URLs and inline event handlers.

(WebCore::CSPDirectiveList::allowScriptNonce):
(WebCore::CSPDirectiveList::parseScriptNonce):
(WebCore::CSPDirectiveList::addDirective):
(WebCore::isAllowedByAllWithNonce):
(WebCore::ContentSecurityPolicy::allowScriptNonce):

  • page/ContentSecurityPolicy.h:

(WebCore):

LayoutTests:

  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-allowed-expected.txt: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-allowed.html: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-badnonce-expected.txt: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-badnonce.html: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-blocked-expected.txt: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-blocked.html: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-emptynonce-expected.txt: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-emptynonce.html: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-scriptsrc-blocked-expected.txt: Added.
  • http/tests/security/contentSecurityPolicy/1.1/scriptnonce-scriptsrc-blocked.html: Added.
  • http/tests/security/contentSecurityPolicy/resources/echo-script-src.pl:

Adding noncy goodness to the echo script.

  • http/tests/security/contentSecurityPolicy/resources/multiple-iframe-test.js:

(test):

  • platform/gtk/TestExpectations:

Skipping 1.1 tests on GTK (missed it in r121879).

Location:
trunk
Files:
11 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r121879 r121883  
     12012-07-04  Mike West  <mkwst@chromium.org>
     2
     3        Implement the script-nonce Content Security Policy directive.
     4        https://bugs.webkit.org/show_bug.cgi?id=89577
     5
     6        Reviewed by Adam Barth.
     7
     8        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-allowed-expected.txt: Added.
     9        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-allowed.html: Added.
     10        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-badnonce-expected.txt: Added.
     11        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-badnonce.html: Added.
     12        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-blocked-expected.txt: Added.
     13        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-blocked.html: Added.
     14        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-emptynonce-expected.txt: Added.
     15        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-emptynonce.html: Added.
     16        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-scriptsrc-blocked-expected.txt: Added.
     17        * http/tests/security/contentSecurityPolicy/1.1/scriptnonce-scriptsrc-blocked.html: Added.
     18        * http/tests/security/contentSecurityPolicy/resources/echo-script-src.pl:
     19            Adding noncy goodness to the echo script.
     20        * http/tests/security/contentSecurityPolicy/resources/multiple-iframe-test.js:
     21        (test):
     22        * platform/gtk/TestExpectations:
     23            Skipping 1.1 tests on GTK (missed it in r121879).
     24
    1252012-07-04  Mike West  <mkwst@chromium.org>
    226
  • trunk/LayoutTests/http/tests/security/contentSecurityPolicy/resources/echo-script-src.pl

    r78058 r121883  
    1111($text, $replacement) = ($replacement, $text) if $cgi->param('should_run') eq 'no';
    1212
     13my $nonce = "";
     14if ($cgi->param('nonce') ne '') {
     15  $nonce = "nonce='".$cgi->param('nonce')."'";
     16}
     17
     18
    1319print "<!DOCTYPE html>\n";
    1420print "<html>\n";
     
    1723print "$text\n";
    1824print "</div>\n";
    19 print "<script src=\"".$cgi->param('q')."\"></script>\n";
     25print "<script $nonce src=\"".$cgi->param('q')."\"></script>\n";
    2026print "</body>\n";
    2127print "</html>\n";
  • trunk/LayoutTests/http/tests/security/contentSecurityPolicy/resources/multiple-iframe-test.js

    r120174 r121883  
    1616                 "&csp=" + escape(current[1]) +
    1717                 "&q=" + baseURL + escape(current[2]);
     18    if (current[3])
     19      iframe.src += "&nonce=" + escape(current[3]);
     20
    1821    iframe.onload = test;
    1922    document.body.appendChild(iframe);
  • trunk/LayoutTests/platform/gtk/TestExpectations

    r121874 r121883  
    342342// Disable webaudio codec tests, including proprietary codecs.
    343343BUGWK88794 SKIP : webaudio/codec-tests = PASS
     344
     345// Content Security Policy 1.1 (ENABLE_CSP_NEXT) is not enabled
     346BUGWK85558 SKIP : http/tests/security/contentSecurityPolicy/1.1 = TEXT
    344347
    345348//////////////////////////////////////////////////////////////////////////////////////////
  • trunk/Source/WebCore/ChangeLog

    r121882 r121883  
     12012-07-04  Mike West  <mkwst@chromium.org>
     2
     3        Implement the script-nonce Content Security Policy directive.
     4        https://bugs.webkit.org/show_bug.cgi?id=89577
     5
     6        Reviewed by Adam Barth.
     7
     8        This patch implements the (experimental) script-nonce Content Security
     9        Policy directive from the 1.1 spec, which allows for selective
     10        execution of script by specifying a "nonce" attribute for the
     11        script tag. Script is only loaded and executed if it both matches the
     12        nonce and matches the script-src whitelist (if present).
     13
     14        The implementation is gated on the ENABLE_CSP_NEXT flag, which is
     15        currently disabled for all ports other than Chromium.
     16
     17        Spec: https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#script-nonce--experimental
     18
     19        Tests: http/tests/security/contentSecurityPolicy/1.1/scriptnonce-allowed.html
     20               http/tests/security/contentSecurityPolicy/1.1/scriptnonce-badnonce.html
     21               http/tests/security/contentSecurityPolicy/1.1/scriptnonce-blocked.html
     22               http/tests/security/contentSecurityPolicy/1.1/scriptnonce-emptynonce.html
     23               http/tests/security/contentSecurityPolicy/1.1/scriptnonce-scriptsrc-blocked.html
     24
     25        * dom/ScriptElement.cpp:
     26        (WebCore::ScriptElement::requestScript):
     27        (WebCore::ScriptElement::executeScript):
     28            Passing the nonce attribute through to check against CSP.
     29        * html/HTMLAttributeNames.in:
     30        * html/HTMLScriptElement.idl:
     31            Adding the `nonce` attribute to the script tag.
     32        * page/ContentSecurityPolicy.cpp:
     33        (CSPDirectiveList):
     34        (WebCore::CSPDirectiveList::logInvalidNonce):
     35        (WebCore):
     36        (WebCore::CSPDirectiveList::checkNonceAndReportViolation):
     37        (WebCore::CSPDirectiveList::allowJavaScriptURLs):
     38        (WebCore::CSPDirectiveList::allowInlineEventHandlers):
     39            If a nonce is set, deny JavaScript URLs and inline event handlers.
     40        (WebCore::CSPDirectiveList::allowScriptNonce):
     41        (WebCore::CSPDirectiveList::parseScriptNonce):
     42        (WebCore::CSPDirectiveList::addDirective):
     43        (WebCore::isAllowedByAllWithNonce):
     44        (WebCore::ContentSecurityPolicy::allowScriptNonce):
     45        * page/ContentSecurityPolicy.h:
     46        (WebCore):
     47
    1482012-07-04  Gyuyoung Kim  <gyuyoung.kim@samsung.com>
    249
  • trunk/Source/WebCore/dom/ScriptElement.cpp

    r118585 r121883  
    251251    if (!m_element->inDocument() || m_element->document() != originalDocument)
    252252        return false;
     253    if (!m_element->document()->contentSecurityPolicy()->allowScriptNonce(m_element->fastGetAttribute(HTMLNames::nonceAttr), m_element->document()->url(), m_startLineNumber, m_element->document()->completeURL(sourceUrl)))
     254        return false;
    253255
    254256    ASSERT(!m_cachedScript);
     
    280282
    281283    if (sourceCode.isEmpty())
     284        return;
     285
     286    if (!m_element->document()->contentSecurityPolicy()->allowScriptNonce(m_element->fastGetAttribute(HTMLNames::nonceAttr), m_element->document()->url(), m_startLineNumber))
    282287        return;
    283288
  • trunk/Source/WebCore/html/HTMLAttributeNames.in

    r117662 r121883  
    159159name
    160160nohref
     161nonce
    161162noresize
    162163noshade
  • trunk/Source/WebCore/html/HTMLScriptElement.idl

    r111359 r121883  
    3030        attribute [Reflect] DOMString type;
    3131        attribute [Reflect] DOMString crossOrigin;
     32        attribute [Reflect, Conditional=CSP_NEXT] DOMString nonce;
    3233    };
    3334}
  • trunk/Source/WebCore/page/ContentSecurityPolicy.cpp

    r120684 r121883  
    3434#include "InspectorInstrumentation.h"
    3535#include "InspectorValues.h"
     36#include "KURL.h"
    3637#include "PingLoader.h"
    3738#include "SchemeRegistry.h"
     
    551552    bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine) const;
    552553    bool allowEval(PassRefPtr<ScriptCallStack>) const;
     554    bool allowScriptNonce(const String& nonce, const String& contextURL, const WTF::OrdinalNumber& contextLine, const KURL&) const;
    553555
    554556    bool allowScriptFromSource(const KURL&) const;
     
    568570    bool parseDirective(const UChar* begin, const UChar* end, String& name, String& value);
    569571    void parseReportURI(const String& name, const String& value);
     572    void parseScriptNonce(const String& name, const String& value);
    570573    void addDirective(const String& name, const String& value);
    571574    void applySandboxPolicy(const String& name, const String& sandboxPolicy);
     
    577580    void logUnrecognizedDirective(const String& name) const;
    578581    void logDuplicateDirective(const String& name) const;
     582    void logInvalidNonce(const String& nonce) const;
    579583    bool checkEval(CSPDirective*) const;
    580584
    581585    bool checkInlineAndReportViolation(CSPDirective*, const String& consoleMessage, const String& contextURL, const WTF::OrdinalNumber& contextLine) const;
     586    bool checkNonceAndReportViolation(const String& nonce, const String& consoleMessage, const String& contextURL, const WTF::OrdinalNumber& contextLine) const;
    582587    bool checkEvalAndReportViolation(CSPDirective*, const String& consoleMessage, const String& contextURL = String(), const WTF::OrdinalNumber& contextLine = WTF::OrdinalNumber::beforeFirst(), PassRefPtr<ScriptCallStack> = 0) const;
    583588    bool checkSourceAndReportViolation(CSPDirective*, const KURL&, const String& type) const;
     
    602607
    603608    Vector<KURL> m_reportURIs;
     609    String m_scriptNonce;
    604610};
    605611
     
    691697}
    692698
     699void CSPDirectiveList::logInvalidNonce(const String& nonce) const
     700{
     701    String message = makeString("Ignoring invalid Content Security Policy script nonce: '", nonce, "'.\n");
     702    m_scriptExecutionContext->addConsoleMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message);
     703}
     704
    693705bool CSPDirectiveList::checkEval(CSPDirective* directive) const
    694706{
     
    706718        return true;
    707719    reportViolation(directive->text(), consoleMessage + "\"" + directive->text() + "\".\n", KURL(), contextURL, contextLine);
     720    return denyIfEnforcingPolicy();
     721}
     722
     723bool CSPDirectiveList::checkNonceAndReportViolation(const String& nonce, const String& consoleMessage, const String& contextURL, const WTF::OrdinalNumber& contextLine) const
     724{
     725    if (m_scriptNonce.isEmpty() || nonce.stripWhiteSpace() == m_scriptNonce)
     726        return true;
     727    reportViolation(m_scriptNonce, consoleMessage + "\"script-nonce " + m_scriptNonce + "\".\n", KURL(), contextURL, contextLine);
    708728    return denyIfEnforcingPolicy();
    709729}
     
    729749{
    730750    DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute JavaScript URL because it violates the following Content Security Policy directive: "));
    731     return checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, contextURL, contextLine);
     751    return (checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, contextURL, contextLine)
     752            && checkNonceAndReportViolation(String(), consoleMessage, contextURL, contextLine));
    732753}
    733754
     
    735756{
    736757    DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute inline event handler because it violates the following Content Security Policy directive: "));
    737     return checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, contextURL, contextLine);
     758    return (checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, contextURL, contextLine)
     759            && checkNonceAndReportViolation(String(), consoleMessage, contextURL, contextLine));
    738760}
    739761
     
    754776    DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to evaluate script because it violates the following Content Security Policy directive: "));
    755777    return checkEvalAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, String(), WTF::OrdinalNumber::beforeFirst(), callStack);
     778}
     779
     780bool CSPDirectiveList::allowScriptNonce(const String& nonce, const String& contextURL, const WTF::OrdinalNumber& contextLine, const KURL& url) const
     781{
     782    DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute script because it violates the following Content Security Policy directive: "));
     783    if (url.isEmpty())
     784        return checkNonceAndReportViolation(nonce, consoleMessage, contextURL, contextLine);
     785    return checkNonceAndReportViolation(nonce, "Refused to load '" + url.string() + "' because it violates the following Content Security Policy directive: ", contextURL, contextLine);
    756786}
    757787
     
    899929}
    900930
     931void CSPDirectiveList::parseScriptNonce(const String& name, const String& value)
     932{
     933    if (!m_scriptNonce.isEmpty()) {
     934        logDuplicateDirective(name);
     935        return;
     936    }
     937
     938    String nonce;
     939    const UChar* position = value.characters();
     940    const UChar* end = position + value.length();
     941
     942    skipWhile<isASCIISpace>(position, end);
     943    const UChar* nonceBegin = position;
     944    if (position == end) {
     945        logInvalidNonce(String());
     946        return;
     947    }
     948    skipWhile<isNotASCIISpace>(position, end);
     949    if (nonceBegin < position)
     950        nonce = String(nonceBegin, position - nonceBegin);
     951
     952    // Trim off trailing whitespace: If we're not at the end of the string, log
     953    // an error.
     954    skipWhile<isASCIISpace>(position, end);
     955    if (position < end)
     956        logInvalidNonce(value);
     957    else
     958        m_scriptNonce = nonce;
     959}
     960
    901961void CSPDirectiveList::setCSPDirective(const String& name, const String& value, OwnPtr<CSPDirective>& directive)
    902962{
     
    922982    DEFINE_STATIC_LOCAL(String, defaultSrc, ("default-src"));
    923983    DEFINE_STATIC_LOCAL(String, scriptSrc, ("script-src"));
     984#if ENABLE(CSP_NEXT)
     985    DEFINE_STATIC_LOCAL(String, scriptNonce, ("script-nonce"));
     986#endif
    924987    DEFINE_STATIC_LOCAL(String, objectSrc, ("object-src"));
    925988    DEFINE_STATIC_LOCAL(String, frameSrc, ("frame-src"));
     
    9561019    else if (equalIgnoringCase(name, reportURI))
    9571020        parseReportURI(name, value);
     1021#if ENABLE(CSP_NEXT)
     1022    else if (equalIgnoringCase(name, scriptNonce))
     1023        parseScriptNonce(name, value);
     1024#endif
    9581025    else
    9591026        logUnrecognizedDirective(name);
     
    10171084}
    10181085
     1086template<bool (CSPDirectiveList::*allowed)(const String&, const String&, const WTF::OrdinalNumber&, const KURL&) const>
     1087bool isAllowedByAllWithNonce(const CSPDirectiveListVector& policies, const String& nonce, const String& contextURL, const WTF::OrdinalNumber& contextLine, const KURL& url)
     1088{
     1089    for (size_t i = 0; i < policies.size(); ++i) {
     1090        if (!(policies[i].get()->*allowed)(nonce, contextURL, contextLine, url))
     1091            return false;
     1092    }
     1093    return true;
     1094}
     1095
    10191096template<bool (CSPDirectiveList::*allowFromURL)(const KURL&) const>
    10201097bool isAllowedByAllWithURL(const CSPDirectiveListVector& policies, const KURL& url)
     
    10571134}
    10581135
     1136bool ContentSecurityPolicy::allowScriptNonce(const String& nonce, const String& contextURL, const WTF::OrdinalNumber& contextLine, const KURL& url) const
     1137{
     1138    return isAllowedByAllWithNonce<&CSPDirectiveList::allowScriptNonce>(m_policies, nonce, contextURL, contextLine, url);
     1139}
     1140
    10591141bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const
    10601142{
  • trunk/Source/WebCore/page/ContentSecurityPolicy.h

    r118585 r121883  
    2727#define ContentSecurityPolicy_h
    2828
     29#include "KURL.h"
    2930#include <wtf/PassOwnPtr.h>
    3031#include <wtf/RefCounted.h>
     
    4142class ScriptCallStack;
    4243class ScriptExecutionContext;
    43 class KURL;
    4444
    4545typedef Vector<OwnPtr<CSPDirectiveList> > CSPDirectiveListVector;
     
    7272    bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine) const;
    7373    bool allowEval(PassRefPtr<ScriptCallStack>) const;
     74    bool allowScriptNonce(const String& nonce, const String& contextURL, const WTF::OrdinalNumber& contextLine, const KURL& = KURL()) const;
    7475
    7576    bool allowScriptFromSource(const KURL&) const;
Note: See TracChangeset for help on using the changeset viewer.