Bug 832193 - Content Security Policy: a source of *.something.com is mistakenly interpreted as a source of http://*:80. r=sstamm, a=akeybl
☠☠ backed out by 40514f725fe8 ☠ ☠
authorIan Melven <imelven@mozilla.com>
Fri, 01 Feb 2013 10:53:20 -0800
changeset 110099 fe099f38e357a0ab2149452af4dc4afa463791c2
parent 110098 0e3e0b0e735f279d0bc371010a3bc7816c993d22
child 110100 c9463a1fa9155cfe7603232c6783283fac7701dd
push id178
push userryanvm@gmail.com
push dateSun, 10 Mar 2013 02:52:54 +0000
reviewerssstamm, akeybl
bugs832193
milestone17.0.4esrpre
Bug 832193 - Content Security Policy: a source of *.something.com is mistakenly interpreted as a source of http://*:80. r=sstamm, a=akeybl
content/base/src/CSPUtils.jsm
content/base/test/unit/test_csputils.js
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -40,17 +40,18 @@ const R_GETSCHEME  = new RegExp ("^" + R
 // scheme-source   = scheme ":"
 const R_SCHEMESRC  = new RegExp ("^" + R_SCHEME.source + "\\:$", 'i');
 
 // host-char       = ALPHA / DIGIT / "-"
 const R_HOSTCHAR   = new RegExp ("[a-zA-Z0-9\\-]", 'i');
 
 // host            = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
 const R_HOST       = new RegExp ("\\*|(((\\*\\.)?" + R_HOSTCHAR.source +
-                                      "+)(\\." + R_HOSTCHAR.source +"+)*)",'i');
+                              "+)" + "(\\." + R_HOSTCHAR.source + "+)*)", 'i');
+
 // port            = ":" ( 1*DIGIT / "*" )
 const R_PORT       = new RegExp ("(\\:([0-9]+|\\*))", 'i');
 
 // host-source     = [ scheme "://" ] host [ port ]
 const R_HOSTSRC    = new RegExp ("^((" + R_SCHEME.source + "\\:\\/\\/)?("
                                        +   R_HOST.source + ")"
                                        +   R_PORT.source + "?)$", 'i');
 
@@ -981,17 +982,18 @@ CSPSource.fromString = function(aStr, se
     CSPError(CSPLocalizer.getStr("selfDataNotProvided"));
     return null;
   }
 
   if (self && !(self instanceof CSPSource)) {
     self = CSPSource.create(self, undefined, false);
   }
 
-  // check for scheme-source match
+  // Check for scheme-source match - this only matches if the source
+  // string is just a scheme with no host.
   if (R_SCHEMESRC.test(aStr)){
     var schemeSrcMatch = R_GETSCHEME.exec(aStr);
     sObj._scheme = schemeSrcMatch[0];
     if (!sObj._host) sObj._host = CSPHost.fromString("*");
     if (!sObj._port) sObj._port = "*";
     return sObj;
   }
 
@@ -1003,26 +1005,32 @@ CSPSource.fromString = function(aStr, se
     if (!schemeMatch || aStr.indexOf("://") == -1) {
       sObj._scheme = self.scheme;
       schemeMatch = null;
     } else {
       sObj._scheme = schemeMatch[0];
     }
 
     // get array of matches to the R_HOST regular expression
-    var hostMatch = R_HOST.exec(aStr);
+    var hostMatch = R_HOSTSRC.exec(aStr);
     if (!hostMatch){
       CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource", [aStr]));
       return null;
     }
-    // host regex gets scheme, so remove scheme from aStr. Add 3 for '://'
+    // Host regex gets scheme, so remove scheme from aStr. Add 3 for '://'
     if (schemeMatch)
-      hostMatch = R_HOST.exec(aStr.substring(schemeMatch[0].length + 3));
+      hostMatch = R_HOSTSRC.exec(aStr.substring(schemeMatch[0].length + 3));
+
+    var portMatch = R_PORT.exec(hostMatch);
+
+    // Host regex also gets port, so remove the port here.
+    if (portMatch)
+      hostMatch = R_HOSTSRC.exec(hostMatch[0].substring(0, hostMatch[0].length - portMatch[0].length));
+
     sObj._host = CSPHost.fromString(hostMatch[0]);
-    var portMatch = R_PORT.exec(aStr);
     if (!portMatch) {
       // gets the default port for the given scheme
       defPort = Services.io.getProtocolHandler(sObj._scheme).defaultPort;
       if (!defPort) {
         CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource", [aStr]));
         return null;
       }
       sObj._port = defPort;
--- a/content/base/test/unit/test_csputils.js
+++ b/content/base/test/unit/test_csputils.js
@@ -272,16 +272,20 @@ test(
 test(
     function test_CSPSourceList_permits() {
       var nullSourceList = CSPSourceList.fromString("'none'");
       var simpleSourceList = CSPSourceList.fromString("a.com", URI("http://self.com"));
       var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88",
                                                       URI("http://self.com:88"));
       var allSourceList = CSPSourceList.fromString("*");
       var allAndMoreSourceList = CSPSourceList.fromString("* https://bar.com 'none'");
+      var wildcardHostSourceList = CSPSourceList.fromString("*.foo.com",
+                                                            undefined, URI("http://self.com"));
+      var allDoubledHostSourceList = CSPSourceList.fromString("**");
+      var allGarbageHostSourceList = CSPSourceList.fromString("*a");
 
       //'none' should permit none."
       do_check_false( nullSourceList.permits("http://a.com"));
       //a.com should permit a.com"
       do_check_true( simpleSourceList.permits("http://a.com"));
       //wrong host"
       do_check_false( simpleSourceList.permits("http://b.com"));
       //double list permits http://bar.com:88"
@@ -297,16 +301,26 @@ test(
 
       //"* does not permit specific host"
       do_check_true( allSourceList.permits("http://x.com:23"));
       //"* does not permit a long host with no port"
       do_check_true( allSourceList.permits("http://a.b.c.d.e.f.g.h.i.j.k.l.x.com"));
 
       //* short circuts parsing
       do_check_true(allAndMoreSourceList.permits("http://a.com"));
+
+      //"** permits all"
+      do_check_false(allDoubledHostSourceList.permits("http://barbaz.com"));
+      //"*a permits all"
+      do_check_false(allGarbageHostSourceList.permits("http://barbaz.com"));
+
+      //"*.foo.com does not permit somerandom.foo.com"
+      do_check_true(wildcardHostSourceList.permits("http://somerandom.foo.com"));
+      //"*.foo.com permits all"
+      do_check_false(wildcardHostSourceList.permits("http://barbaz.com"));
     });
 
 test(
     function test_CSPSourceList_intersect() {
       // for this test, 'self' values are irrelevant
       // policy a /\ policy b intersects policies, not context (where 'self'
       // values come into play)
       var nullSourceList = CSPSourceList.fromString("'none'");