Title: [258631] trunk
Revision
258631
Author
[email protected]
Date
2020-03-18 07:49:37 -0700 (Wed, 18 Mar 2020)

Log Message

Make sure a preflight fails if response headers are invalid
https://bugs.webkit.org/show_bug.cgi?id=208924

Reviewed by Alex Christensen.

LayoutTests/imported/w3c:

* web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any-expected.txt: Added.
* web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.html: Added.
* web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.js: Added.
(corsPreflightResponseValidation):
* web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker-expected.txt: Added.
* web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker.html: Added.

Source/WebCore:

Implement https://fetch.spec.whatwg.org/#cors-preflight-fetch-0 step 7.3.
In case header parsing is wrong, fail the preflight with a meaningful message.
Update parsing of headers to return an Optional so that parsing error is handled as a nullopt.
Minor refactoring to return Expected/Optional for error handlng instead of passing an out parameter.
Also, adding preflight cache entry if it is valid, no matter whether preflight succeeds or not.

Tests: imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.html
       imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker.html

* loader/CrossOriginAccessControl.cpp:
(WebCore::validatePreflightResponse):
* loader/CrossOriginPreflightResultCache.cpp:
(WebCore::CrossOriginPreflightResultCacheItem::create):
(WebCore::CrossOriginPreflightResultCacheItem::validateMethodAndHeaders const):
* loader/CrossOriginPreflightResultCache.h:
(WebCore::CrossOriginPreflightResultCacheItem::CrossOriginPreflightResultCacheItem):
* platform/network/HTTPParsers.h:
(WebCore::parseAccessControlAllowList):
* platform/network/ResourceResponseBase.cpp:
(WebCore::ResourceResponseBase::filter):
(WebCore::ResourceResponseBase::sanitizeHTTPHeaderFieldsAccordingToTainting):

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (258630 => 258631)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2020-03-18 14:28:46 UTC (rev 258630)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2020-03-18 14:49:37 UTC (rev 258631)
@@ -1,3 +1,17 @@
+2020-03-18  youenn fablet  <[email protected]>
+
+        Make sure a preflight fails if response headers are invalid
+        https://bugs.webkit.org/show_bug.cgi?id=208924
+
+        Reviewed by Alex Christensen.
+
+        * web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any-expected.txt: Added.
+        * web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.html: Added.
+        * web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.js: Added.
+        (corsPreflightResponseValidation):
+        * web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker-expected.txt: Added.
+        * web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker.html: Added.
+
 2020-03-17  Frederic Wang  <[email protected]>
 
         Update wpt tests imported/w3c/web-platform-tests/html/rendering/non-replaced-elements/the-page

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any-expected.txt (0 => 258631)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any-expected.txt	2020-03-18 14:49:37 UTC (rev 258631)
@@ -0,0 +1,4 @@
+
+PASS Preflight response with a bad Access-Control-Allow-Headers 
+PASS Preflight response with a bad Access-Control-Allow-Methods 
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.html (0 => 258631)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.html	2020-03-18 14:49:37 UTC (rev 258631)
@@ -0,0 +1 @@
+<!-- This file is required for WebKit test infrastructure to run the templated test --> 

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.js (0 => 258631)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.js	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.js	2020-03-18 14:49:37 UTC (rev 258631)
@@ -0,0 +1,34 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsPreflightResponseValidation(desc, corsUrl, allowHeaders, allowMethods) {
+  var uuid_token = token();
+  var url = ""
+  var requestInit = {"mode": "cors"};
+  /* Force preflight */
+  requestInit["headers"] = {"x-force-preflight": ""};
+
+  var urlParameters = "?token=" + uuid_token + "&max_age=0";
+  urlParameters += "&allow_headers=x-force-preflight";
+  if (allowHeaders)
+    urlParameters += "," + allowHeaders;
+  if (allowMethods)
+    urlParameters += "&allow_methods="+ allowMethods;
+
+  promise_test(function(test) {
+    return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(async function(resp) {
+      assert_equals(resp.status, 200, "Clean stash response's status is 200");
+      await promise_rejects_js(test, TypeError, fetch(url + urlParameters, requestInit));
+
+      return fetch(url + urlParameters).then(function(resp) {
+        assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+      });
+    });
+  }, desc);
+}
+
+var corsUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+corsPreflightResponseValidation("Preflight response with a bad Access-Control-Allow-Headers", corsUrl, "Bad value", null);
+corsPreflightResponseValidation("Preflight response with a bad Access-Control-Allow-Methods", corsUrl, null, "Bad value");
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker-expected.txt (0 => 258631)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker-expected.txt	2020-03-18 14:49:37 UTC (rev 258631)
@@ -0,0 +1,4 @@
+
+PASS Preflight response with a bad Access-Control-Allow-Headers 
+PASS Preflight response with a bad Access-Control-Allow-Methods 
+

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker.html (0 => 258631)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker.html	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker.html	2020-03-18 14:49:37 UTC (rev 258631)
@@ -0,0 +1 @@
+<!-- This file is required for WebKit test infrastructure to run the templated test --> 

Modified: trunk/Source/WebCore/ChangeLog (258630 => 258631)


--- trunk/Source/WebCore/ChangeLog	2020-03-18 14:28:46 UTC (rev 258630)
+++ trunk/Source/WebCore/ChangeLog	2020-03-18 14:49:37 UTC (rev 258631)
@@ -1,3 +1,32 @@
+2020-03-18  youenn fablet  <[email protected]>
+
+        Make sure a preflight fails if response headers are invalid
+        https://bugs.webkit.org/show_bug.cgi?id=208924
+
+        Reviewed by Alex Christensen.
+
+        Implement https://fetch.spec.whatwg.org/#cors-preflight-fetch-0 step 7.3.
+        In case header parsing is wrong, fail the preflight with a meaningful message.
+        Update parsing of headers to return an Optional so that parsing error is handled as a nullopt.
+        Minor refactoring to return Expected/Optional for error handlng instead of passing an out parameter.
+        Also, adding preflight cache entry if it is valid, no matter whether preflight succeeds or not.
+
+        Tests: imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.html
+               imported/w3c/web-platform-tests/fetch/api/cors/cors-preflight-response-validation.any.worker.html
+
+        * loader/CrossOriginAccessControl.cpp:
+        (WebCore::validatePreflightResponse):
+        * loader/CrossOriginPreflightResultCache.cpp:
+        (WebCore::CrossOriginPreflightResultCacheItem::create):
+        (WebCore::CrossOriginPreflightResultCacheItem::validateMethodAndHeaders const):
+        * loader/CrossOriginPreflightResultCache.h:
+        (WebCore::CrossOriginPreflightResultCacheItem::CrossOriginPreflightResultCacheItem):
+        * platform/network/HTTPParsers.h:
+        (WebCore::parseAccessControlAllowList):
+        * platform/network/ResourceResponseBase.cpp:
+        (WebCore::ResourceResponseBase::filter):
+        (WebCore::ResourceResponseBase::sanitizeHTTPHeaderFieldsAccordingToTainting):
+
 2020-03-18  Joonghun Park  <[email protected]>
 
         Unreviewed. Remove the build warning below since r258458

Modified: trunk/Source/WebCore/loader/CrossOriginAccessControl.cpp (258630 => 258631)


--- trunk/Source/WebCore/loader/CrossOriginAccessControl.cpp	2020-03-18 14:28:46 UTC (rev 258630)
+++ trunk/Source/WebCore/loader/CrossOriginAccessControl.cpp	2020-03-18 14:49:37 UTC (rev 258631)
@@ -264,16 +264,17 @@
     if (!accessControlCheckResult)
         return accessControlCheckResult;
 
-    auto result = makeUnique<CrossOriginPreflightResultCacheItem>(storedCredentialsPolicy);
-    String errorDescription;
-    // FIXME: allowsCrossOriginMethod and allowsCrossOriginHeaders should return Expected<void, String> to avoid having an out parameter.
-    if (!result->parse(response)
-        || !result->allowsCrossOriginMethod(request.httpMethod(), storedCredentialsPolicy, errorDescription)
-        || !result->allowsCrossOriginHeaders(request.httpHeaderFields(), storedCredentialsPolicy, errorDescription)) {
-        return makeUnexpected(errorDescription);
-    }
+    auto result = CrossOriginPreflightResultCacheItem::create(storedCredentialsPolicy, response);
+    if (!result.has_value())
+        return makeUnexpected(WTFMove(result.error()));
 
-    CrossOriginPreflightResultCache::singleton().appendEntry(securityOrigin.toString(), request.url(), WTFMove(result));
+    auto entry = WTFMove(result.value());
+    auto errorDescription = entry->validateMethodAndHeaders(request.httpMethod(), request.httpHeaderFields());
+    CrossOriginPreflightResultCache::singleton().appendEntry(securityOrigin.toString(), request.url(), entry.moveToUniquePtr());
+
+    if (errorDescription)
+        return makeUnexpected(WTFMove(*errorDescription));
+
     return { };
 }
 

Modified: trunk/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp (258630 => 258631)


--- trunk/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp	2020-03-18 14:28:46 UTC (rev 258630)
+++ trunk/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp	2020-03-18 14:49:37 UTC (rev 258631)
@@ -52,11 +52,16 @@
     return ok;
 }
 
-bool CrossOriginPreflightResultCacheItem::parse(const ResourceResponse& response)
+Expected<UniqueRef<CrossOriginPreflightResultCacheItem>, String> CrossOriginPreflightResultCacheItem::create(StoredCredentialsPolicy policy, const ResourceResponse& response)
 {
-    m_methods = parseAccessControlAllowList(response.httpHeaderField(HTTPHeaderName::AccessControlAllowMethods));
-    m_headers = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(response.httpHeaderField(HTTPHeaderName::AccessControlAllowHeaders));
+    auto methods = parseAccessControlAllowList(response.httpHeaderField(HTTPHeaderName::AccessControlAllowMethods));
+    if (!methods)
+        return makeUnexpected(makeString("Header Access-Control-Allow-Methods has an invalid value: ", response.httpHeaderField(HTTPHeaderName::AccessControlAllowMethods)));
 
+    auto headers = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(response.httpHeaderField(HTTPHeaderName::AccessControlAllowHeaders));
+    if (!headers)
+        return makeUnexpected(makeString("Header Access-Control-Allow-Headers has an invalid value: ", response.httpHeaderField(HTTPHeaderName::AccessControlAllowHeaders)));
+
     Seconds expiryDelta = 0_s;
     if (parseAccessControlMaxAge(response.httpHeaderField(HTTPHeaderName::AccessControlMaxAge), expiryDelta)) {
         if (expiryDelta > maxPreflightCacheTimeout)
@@ -64,10 +69,19 @@
     } else
         expiryDelta = defaultPreflightCacheTimeout;
 
-    m_absoluteExpiryTime = MonotonicTime::now() + expiryDelta;
-    return true;
+    return makeUniqueRef<CrossOriginPreflightResultCacheItem>(MonotonicTime::now() + expiryDelta, policy, WTFMove(*methods), WTFMove(*headers));
 }
 
+Optional<String> CrossOriginPreflightResultCacheItem::validateMethodAndHeaders(const String& method, const HTTPHeaderMap& requestHeaders) const
+{
+    String errorDescription;
+    if (!allowsCrossOriginMethod(method, m_storedCredentialsPolicy, errorDescription))
+        return WTFMove(errorDescription);
+    if (!allowsCrossOriginHeaders(requestHeaders, m_storedCredentialsPolicy, errorDescription))
+        return WTFMove(errorDescription);
+    return { };
+}
+
 bool CrossOriginPreflightResultCacheItem::allowsCrossOriginMethod(const String& method, StoredCredentialsPolicy storedCredentialsPolicy, String& errorDescription) const
 {
     if (m_methods.contains(method) || (m_methods.contains("*") && storedCredentialsPolicy != StoredCredentialsPolicy::Use) || isOnAccessControlSimpleRequestMethodWhitelist(method))

Modified: trunk/Source/WebCore/loader/CrossOriginPreflightResultCache.h (258630 => 258631)


--- trunk/Source/WebCore/loader/CrossOriginPreflightResultCache.h	2020-03-18 14:28:46 UTC (rev 258630)
+++ trunk/Source/WebCore/loader/CrossOriginPreflightResultCache.h	2020-03-18 14:49:37 UTC (rev 258631)
@@ -27,10 +27,12 @@
 #pragma once
 
 #include "StoredCredentialsPolicy.h"
+#include <wtf/Expected.h>
 #include <wtf/HashMap.h>
 #include <wtf/HashSet.h>
 #include <wtf/MonotonicTime.h>
 #include <wtf/URLHash.h>
+#include <wtf/UniqueRef.h>
 
 namespace WebCore {
 
@@ -40,17 +42,17 @@
 class CrossOriginPreflightResultCacheItem {
     WTF_MAKE_NONCOPYABLE(CrossOriginPreflightResultCacheItem); WTF_MAKE_FAST_ALLOCATED;
 public:
-    explicit CrossOriginPreflightResultCacheItem(StoredCredentialsPolicy storedCredentialsPolicy)
-        : m_storedCredentialsPolicy(storedCredentialsPolicy)
-    {
-    }
+    static Expected<UniqueRef<CrossOriginPreflightResultCacheItem>, String> create(StoredCredentialsPolicy, const ResourceResponse&);
 
-    WEBCORE_EXPORT bool parse(const ResourceResponse&);
-    WEBCORE_EXPORT bool allowsCrossOriginMethod(const String&, StoredCredentialsPolicy, String& errorDescription) const;
-    WEBCORE_EXPORT bool allowsCrossOriginHeaders(const HTTPHeaderMap&, StoredCredentialsPolicy, String& errorDescription) const;
-    bool allowsRequest(StoredCredentialsPolicy, const String& method, const HTTPHeaderMap& requestHeaders) const;
+    CrossOriginPreflightResultCacheItem(MonotonicTime, StoredCredentialsPolicy, HashSet<String>&&, HashSet<String, ASCIICaseInsensitiveHash>&&);
 
+    Optional<String> validateMethodAndHeaders(const String& method, const HTTPHeaderMap&) const;
+    bool allowsRequest(StoredCredentialsPolicy, const String& method, const HTTPHeaderMap&) const;
+
 private:
+    bool allowsCrossOriginMethod(const String&, StoredCredentialsPolicy, String& errorDescription) const;
+    bool allowsCrossOriginHeaders(const HTTPHeaderMap&, StoredCredentialsPolicy, String& errorDescription) const;
+
     // FIXME: A better solution to holding onto the absolute expiration time might be
     // to start a timer for the expiration delta that removes this from the cache when
     // it fires.
@@ -75,4 +77,12 @@
     HashMap<std::pair<String, URL>, std::unique_ptr<CrossOriginPreflightResultCacheItem>> m_preflightHashMap;
 };
 
+inline CrossOriginPreflightResultCacheItem::CrossOriginPreflightResultCacheItem(MonotonicTime absoluteExpiryTime, StoredCredentialsPolicy  storedCredentialsPolicy, HashSet<String>&& methods, HashSet<String, ASCIICaseInsensitiveHash>&& headers)
+    : m_absoluteExpiryTime(absoluteExpiryTime)
+    , m_storedCredentialsPolicy(storedCredentialsPolicy)
+    , m_methods(WTFMove(methods))
+    , m_headers(WTFMove(headers))
+{
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/platform/network/HTTPParsers.h (258630 => 258631)


--- trunk/Source/WebCore/platform/network/HTTPParsers.h	2020-03-18 14:28:46 UTC (rev 258630)
+++ trunk/Source/WebCore/platform/network/HTTPParsers.h	2020-03-18 14:49:37 UTC (rev 258631)
@@ -156,7 +156,7 @@
 }
 
 template<class HashType = DefaultHash<String>::Hash>
-HashSet<String, HashType> parseAccessControlAllowList(const String& string)
+Optional<HashSet<String, HashType>> parseAccessControlAllowList(const String& string)
 {
     HashSet<String, HashType> set;
     unsigned start = 0;
@@ -172,7 +172,7 @@
         if (!addToAccessControlAllowList(string, start, string.length() - 1, set))
             return { };
     }
-    return set;
+    return WTFMove(set);
 }
 
 }

Modified: trunk/Source/WebCore/platform/network/ResourceResponseBase.cpp (258630 => 258631)


--- trunk/Source/WebCore/platform/network/ResourceResponseBase.cpp	2020-03-18 14:28:46 UTC (rev 258630)
+++ trunk/Source/WebCore/platform/network/ResourceResponseBase.cpp	2020-03-18 14:49:37 UTC (rev 258631)
@@ -180,7 +180,7 @@
     ASSERT(response.tainting() == Tainting::Cors);
     filteredResponse.setType(Type::Cors);
 
-    auto accessControlExposeHeaderSet = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders));
+    auto accessControlExposeHeaderSet = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders)).valueOr(HashSet<String, ASCIICaseInsensitiveHash> { });
     if (performCheck == PerformExposeAllHeadersCheck::Yes && accessControlExposeHeaderSet.contains("*"))
         return filteredResponse;
 
@@ -446,7 +446,7 @@
     case ResourceResponse::Tainting::Basic:
         return;
     case ResourceResponse::Tainting::Cors: {
-        auto corsSafeHeaderSet = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders));
+        auto corsSafeHeaderSet = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders)).valueOr(HashSet<String, ASCIICaseInsensitiveHash> { });
         if (corsSafeHeaderSet.contains("*"))
             return;
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to