Title: [193830] trunk
Revision
193830
Author
ma...@webkit.org
Date
2015-12-09 06:52:46 -0800 (Wed, 09 Dec 2015)

Log Message

[GTK] Crash in WebProcess when loading large content with custom URI schemes
https://bugs.webkit.org/show_bug.cgi?id=144262

Reviewed by Carlos Garcia Campos.

Source/WebKit2:

Properly handle scenarios where errors happen after reading the first
chunk of data coming from the GInputStream provided by the application.

* UIProcess/API/gtk/WebKitWebContextPrivate.h:
* UIProcess/API/gtk/WebKitWebContext.cpp:
(webkitWebContextIsLoadingCustomProtocol): New, checks whether a load
is still in progress, after the startLoading method has been called.
* UIProcess/API/gtk/WebKitURISchemeRequest.cpp:
(webkitURISchemeRequestReadCallback): Early return if the stream has been
cancelled on finish_error, so that we make sure we don't keep on reading
the GInputStream after that point.
(webkit_uri_scheme_request_finish_error): Don't send a didFailWithError
message to the Network process if the load is not longer in progress.
* Shared/Network/CustomProtocols/soup/CustomProtocolManagerImpl.cpp:
(WebKit::CustomProtocolManagerImpl::didFailWithError): Handle the case where
an error is notified from the UI process after the first chunk has been read.
(WebKit::CustomProtocolManagerImpl::didReceiveResponse): Handle the case where
data might no longer be available if an error happened even before this point.
* WebProcess/soup/WebKitSoupRequestInputStream.h:
* WebProcess/soup/WebKitSoupRequestInputStream.cpp:
(webkitSoupRequestInputStreamDidFailWithError): Notify the custom GInputStream
that we no longer want to keep reading data in chunks due to a specific error.
(webkitSoupRequestInputStreamReadAsync): Early finish the GTask with a specific
error whenever webkitSoupRequestInputStreamDidFailWithError() has been called.

Tools:

Added new unit test to check the additional scenarios we now
handle for custom URI schemes.

* TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebContext.cpp:
(generateHTMLContent): New helper function to generate big enough content.
(testWebContextURIScheme): New unit test.

Modified Paths

Diff

Modified: trunk/Source/WebKit2/ChangeLog (193829 => 193830)


--- trunk/Source/WebKit2/ChangeLog	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Source/WebKit2/ChangeLog	2015-12-09 14:52:46 UTC (rev 193830)
@@ -1,3 +1,35 @@
+2015-12-09  Mario Sanchez Prada  <ma...@endlessm.com>
+
+        [GTK] Crash in WebProcess when loading large content with custom URI schemes
+        https://bugs.webkit.org/show_bug.cgi?id=144262
+
+        Reviewed by Carlos Garcia Campos.
+
+        Properly handle scenarios where errors happen after reading the first
+        chunk of data coming from the GInputStream provided by the application.
+
+        * UIProcess/API/gtk/WebKitWebContextPrivate.h:
+        * UIProcess/API/gtk/WebKitWebContext.cpp:
+        (webkitWebContextIsLoadingCustomProtocol): New, checks whether a load
+        is still in progress, after the startLoading method has been called.
+        * UIProcess/API/gtk/WebKitURISchemeRequest.cpp:
+        (webkitURISchemeRequestReadCallback): Early return if the stream has been
+        cancelled on finish_error, so that we make sure we don't keep on reading
+        the GInputStream after that point.
+        (webkit_uri_scheme_request_finish_error): Don't send a didFailWithError
+        message to the Network process if the load is not longer in progress.
+        * Shared/Network/CustomProtocols/soup/CustomProtocolManagerImpl.cpp:
+        (WebKit::CustomProtocolManagerImpl::didFailWithError): Handle the case where
+        an error is notified from the UI process after the first chunk has been read.
+        (WebKit::CustomProtocolManagerImpl::didReceiveResponse): Handle the case where
+        data might no longer be available if an error happened even before this point.
+        * WebProcess/soup/WebKitSoupRequestInputStream.h:
+        * WebProcess/soup/WebKitSoupRequestInputStream.cpp:
+        (webkitSoupRequestInputStreamDidFailWithError): Notify the custom GInputStream
+        that we no longer want to keep reading data in chunks due to a specific error.
+        (webkitSoupRequestInputStreamReadAsync): Early finish the GTask with a specific
+        error whenever webkitSoupRequestInputStreamDidFailWithError() has been called.
+
 2015-12-09  Ryuan Choi  <ryuan.c...@navercorp.com>
 
         [CoordinatedGraphics][EFL] Fix unhandled web process message when launching MiniBrowser

Modified: trunk/Source/WebKit2/Shared/Network/CustomProtocols/soup/CustomProtocolManagerImpl.cpp (193829 => 193830)


--- trunk/Source/WebKit2/Shared/Network/CustomProtocols/soup/CustomProtocolManagerImpl.cpp	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Source/WebKit2/Shared/Network/CustomProtocols/soup/CustomProtocolManagerImpl.cpp	2015-12-09 14:52:46 UTC (rev 193830)
@@ -116,11 +116,18 @@
     WebSoupRequestAsyncData* data = ""
     ASSERT(data);
 
-    GRefPtr<GTask> task = data->releaseTask();
-    ASSERT(task.get());
-    g_task_return_new_error(task.get(), g_quark_from_string(error.domain().utf8().data()),
-        error.errorCode(), "%s", error.localizedDescription().utf8().data());
+    // Either we haven't started reading the stream yet, in which case we need to complete the
+    // task first, or we failed reading it and the task was already completed by didLoadData().
+    ASSERT(!data->stream || !data->task);
 
+    if (!data->stream) {
+        GRefPtr<GTask> task = data->releaseTask();
+        ASSERT(task.get());
+        g_task_return_new_error(task.get(), g_quark_from_string(error.domain().utf8().data()),
+            error.errorCode(), "%s", error.localizedDescription().utf8().data());
+    } else
+        webkitSoupRequestInputStreamDidFailWithError(WEBKIT_SOUP_REQUEST_INPUT_STREAM(data->stream.get()), error);
+
     m_customProtocolMap.remove(customProtocolID);
 }
 
@@ -170,7 +177,10 @@
 void CustomProtocolManagerImpl::didReceiveResponse(uint64_t customProtocolID, const WebCore::ResourceResponse& response)
 {
     WebSoupRequestAsyncData* data = ""
-    ASSERT(data);
+    // The data might have been removed from the request map if an error happened even before this point.
+    if (!data)
+        return;
+
     ASSERT(data->task);
 
     WebKitSoupRequestGeneric* request = WEBKIT_SOUP_REQUEST_GENERIC(g_task_get_source_object(data->task));

Modified: trunk/Source/WebKit2/UIProcess/API/gtk/WebKitURISchemeRequest.cpp (193829 => 193830)


--- trunk/Source/WebKit2/UIProcess/API/gtk/WebKitURISchemeRequest.cpp	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Source/WebKit2/UIProcess/API/gtk/WebKitURISchemeRequest.cpp	2015-12-09 14:52:46 UTC (rev 193830)
@@ -166,6 +166,11 @@
         return;
     }
 
+    // Need to check the stream before proceeding as it can be cancelled if finish_error
+    // was previously call, which won't be detected by g_input_stream_read_finish().
+    if (!request->priv->stream)
+        return;
+
     WebKitURISchemeRequestPrivate* priv = request->priv;
     Ref<API::Data> webData = API::Data::create(reinterpret_cast<const unsigned char*>(priv->readBuffer), bytesRead);
     if (!priv->bytesRead) {
@@ -230,7 +235,10 @@
     g_return_if_fail(error);
 
     WebKitURISchemeRequestPrivate* priv = request->priv;
+    if (!webkitWebContextIsLoadingCustomProtocol(priv->webContext, priv->requestID))
+        return;
 
+    priv->stream = nullptr;
     WebCore::ResourceError resourceError(g_quark_to_string(error->domain), toWebCoreError(error->code), priv->uri.data(), String::fromUTF8(error->message));
     priv->webRequestManager->didFailWithError(priv->requestID, resourceError);
     webkitWebContextDidFinishLoadingCustomProtocol(priv->webContext, priv->requestID);

Modified: trunk/Source/WebKit2/UIProcess/API/gtk/WebKitWebContext.cpp (193829 => 193830)


--- trunk/Source/WebKit2/UIProcess/API/gtk/WebKitWebContext.cpp	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Source/WebKit2/UIProcess/API/gtk/WebKitWebContext.cpp	2015-12-09 14:52:46 UTC (rev 193830)
@@ -1294,6 +1294,11 @@
     context->priv->uriSchemeRequests.remove(customProtocolID);
 }
 
+bool webkitWebContextIsLoadingCustomProtocol(WebKitWebContext* context, uint64_t customProtocolID)
+{
+    return context->priv->uriSchemeRequests.get(customProtocolID);
+}
+
 void webkitWebContextCreatePageForWebView(WebKitWebContext* context, WebKitWebView* webView, WebKitUserContentManager* userContentManager, WebKitWebView* relatedView)
 {
     WebKitWebViewBase* webViewBase = WEBKIT_WEB_VIEW_BASE(webView);

Modified: trunk/Source/WebKit2/UIProcess/API/gtk/WebKitWebContextPrivate.h (193829 => 193830)


--- trunk/Source/WebKit2/UIProcess/API/gtk/WebKitWebContextPrivate.h	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Source/WebKit2/UIProcess/API/gtk/WebKitWebContextPrivate.h	2015-12-09 14:52:46 UTC (rev 193830)
@@ -42,6 +42,7 @@
 void webkitWebContextStartLoadingCustomProtocol(WebKitWebContext*, uint64_t customProtocolID, API::URLRequest*);
 void webkitWebContextStopLoadingCustomProtocol(WebKitWebContext*, uint64_t customProtocolID);
 void webkitWebContextDidFinishLoadingCustomProtocol(WebKitWebContext*, uint64_t customProtocolID);
+bool webkitWebContextIsLoadingCustomProtocol(WebKitWebContext*, uint64_t customProtocolID);
 void webkitWebContextCreatePageForWebView(WebKitWebContext*, WebKitWebView*, WebKitUserContentManager*, WebKitWebView*);
 void webkitWebContextWebViewDestroyed(WebKitWebContext*, WebKitWebView*);
 WebKitWebView* webkitWebContextGetWebViewForPage(WebKitWebContext*, WebKit::WebPageProxy*);

Modified: trunk/Source/WebKit2/WebProcess/soup/WebKitSoupRequestInputStream.cpp (193829 => 193830)


--- trunk/Source/WebKit2/WebProcess/soup/WebKitSoupRequestInputStream.cpp	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Source/WebKit2/WebProcess/soup/WebKitSoupRequestInputStream.cpp	2015-12-09 14:52:46 UTC (rev 193830)
@@ -23,6 +23,7 @@
 #include <wtf/Lock.h>
 #include <wtf/Threading.h>
 #include <wtf/glib/GRefPtr.h>
+#include <wtf/glib/GUniquePtr.h>
 
 struct AsyncReadData {
     AsyncReadData(GTask* task, void* buffer, gsize count)
@@ -42,6 +43,8 @@
     uint64_t bytesReceived;
     uint64_t bytesRead;
 
+    GUniquePtr<GError> error;
+
     Lock readLock;
     std::unique_ptr<AsyncReadData> pendingAsyncRead;
 };
@@ -92,6 +95,11 @@
         return;
     }
 
+    if (stream->priv->error.get()) {
+        g_task_return_error(task.get(), stream->priv->error.release());
+        return;
+    }
+
     if (webkitSoupRequestInputStreamHasDataToRead(stream)) {
         webkitSoupRequestInputStreamReadAsyncResultComplete(task.get(), buffer, count);
         return;
@@ -163,6 +171,18 @@
     webkitSoupRequestInputStreamPendingReadAsyncComplete(stream);
 }
 
+void webkitSoupRequestInputStreamDidFailWithError(WebKitSoupRequestInputStream* stream, const WebCore::ResourceError& resourceError)
+{
+    GUniquePtr<GError> error(g_error_new(g_quark_from_string(resourceError.domain().utf8().data()), resourceError.errorCode(), "%s", resourceError.localizedDescription().utf8().data()));
+    if (stream->priv->pendingAsyncRead) {
+        AsyncReadData* data = ""
+        g_task_return_error(data->task.get(), error.release());
+    } else {
+        stream->priv->contentLength = stream->priv->bytesReceived;
+        stream->priv->error = WTF::move(error);
+    }
+}
+
 bool webkitSoupRequestInputStreamFinished(WebKitSoupRequestInputStream* stream)
 {
     return !webkitSoupRequestInputStreamIsWaitingForData(stream);

Modified: trunk/Source/WebKit2/WebProcess/soup/WebKitSoupRequestInputStream.h (193829 => 193830)


--- trunk/Source/WebKit2/WebProcess/soup/WebKitSoupRequestInputStream.h	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Source/WebKit2/WebProcess/soup/WebKitSoupRequestInputStream.h	2015-12-09 14:52:46 UTC (rev 193830)
@@ -20,6 +20,7 @@
 #ifndef WebKitSoupRequestInputStream_h
 #define WebKitSoupRequestInputStream_h
 
+#include <WebCore/ResourceError.h>
 #include <gio/gio.h>
 
 G_BEGIN_DECLS
@@ -48,6 +49,7 @@
 GType webkit_soup_request_input_stream_get_type();
 GInputStream* webkitSoupRequestInputStreamNew(uint64_t contentLength);
 void webkitSoupRequestInputStreamAddData(WebKitSoupRequestInputStream*, const void* data, size_t dataLength);
+void webkitSoupRequestInputStreamDidFailWithError(WebKitSoupRequestInputStream*, const WebCore::ResourceError&);
 bool webkitSoupRequestInputStreamFinished(WebKitSoupRequestInputStream*);
 
 G_END_DECLS

Modified: trunk/Tools/ChangeLog (193829 => 193830)


--- trunk/Tools/ChangeLog	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Tools/ChangeLog	2015-12-09 14:52:46 UTC (rev 193830)
@@ -1,3 +1,17 @@
+2015-12-09  Mario Sanchez Prada  <ma...@endlessm.com>
+
+        [GTK] Crash in WebProcess when loading large content with custom URI schemes
+        https://bugs.webkit.org/show_bug.cgi?id=144262
+
+        Reviewed by Carlos Garcia Campos.
+
+        Added new unit test to check the additional scenarios we now
+        handle for custom URI schemes.
+
+        * TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebContext.cpp:
+        (generateHTMLContent): New helper function to generate big enough content.
+        (testWebContextURIScheme): New unit test.
+
 2015-12-09  Ryuan Choi  <ryuan.c...@navercorp.com>
 
         [EFL] Fix unhandled web process message when launching MiniBrowser

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebContext.cpp (193829 => 193830)


--- trunk/Tools/TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebContext.cpp	2015-12-09 14:46:11 UTC (rev 193829)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit2Gtk/TestWebKitWebContext.cpp	2015-12-09 14:52:46 UTC (rev 193830)
@@ -26,6 +26,7 @@
 #include <wtf/HashMap.h>
 #include <wtf/glib/GRefPtr.h>
 #include <wtf/glib/GUniquePtr.h>
+#include <wtf/text/StringBuilder.h>
 #include <wtf/text/StringHash.h>
 
 static WebKitTestServer* kServer;
@@ -197,8 +198,11 @@
 static const char* kEchoHTMLFormat = "<html><body>%s</body></html>";
 static const char* errorDomain = "test";
 static const int errorCode = 10;
-static const char* errorMessage = "Error message.";
 
+static const char* genericErrorMessage = "Error message.";
+static const char* beforeReceiveResponseErrorMessage = "Error before didReceiveResponse.";
+static const char* afterInitialChunkErrorMessage = "Error after reading the initial chunk.";
+
 class URISchemeTest: public LoadTrackingTest {
 public:
     MAKE_GLIB_TEST_FIXTURE(URISchemeTest);
@@ -229,23 +233,38 @@
 
         g_assert(webkit_uri_scheme_request_get_web_view(request) == test->m_webView);
 
-        GRefPtr<GInputStream> inputStream = adoptGRef(g_memory_input_stream_new());
-        test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(inputStream.get()));
-
         const char* scheme = webkit_uri_scheme_request_get_scheme(request);
         g_assert(scheme);
         g_assert(test->m_handlersMap.contains(String::fromUTF8(scheme)));
 
+        const URISchemeHandler& handler = test->m_handlersMap.get(String::fromUTF8(scheme));
+
+        GRefPtr<GInputStream> inputStream = adoptGRef(g_memory_input_stream_new());
+        test->assertObjectIsDeletedWhenTestFinishes(G_OBJECT(inputStream.get()));
+
+        const gchar* requestPath = webkit_uri_scheme_request_get_path(request);
+
         if (!g_strcmp0(scheme, "error")) {
-            GUniquePtr<GError> error(g_error_new_literal(g_quark_from_string(errorDomain), errorCode, errorMessage));
-            webkit_uri_scheme_request_finish_error(request, error.get());
+            if (!g_strcmp0(requestPath, "before-response")) {
+                GUniquePtr<GError> error(g_error_new_literal(g_quark_from_string(errorDomain), errorCode, beforeReceiveResponseErrorMessage));
+                // We call finish() and then finish_error() to make sure that not even
+                // the didReceiveResponse message is processed at the time of failing.
+                webkit_uri_scheme_request_finish(request, G_INPUT_STREAM(inputStream.get()), handler.replyLength, handler.mimeType.data());
+                webkit_uri_scheme_request_finish_error(request, error.get());
+            } else if (!g_strcmp0(requestPath, "after-first-chunk")) {
+                g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(inputStream.get()), handler.reply.data(), handler.reply.length(), 0);
+                webkit_uri_scheme_request_finish(request, inputStream.get(), handler.replyLength, handler.mimeType.data());
+                // We need to wait until we reach the load-committed state before calling webkit_uri_scheme_request_finish_error(),
+                // so we rely on the test using finishOnCommittedAndWaitUntilLoadFinished() to actually call it from loadCommitted().
+            } else {
+                GUniquePtr<GError> error(g_error_new_literal(g_quark_from_string(errorDomain), errorCode, genericErrorMessage));
+                webkit_uri_scheme_request_finish_error(request, error.get());
+            }
             return;
         }
 
-        const URISchemeHandler& handler = test->m_handlersMap.get(String::fromUTF8(scheme));
-
         if (!g_strcmp0(scheme, "echo")) {
-            char* replyHTML = g_strdup_printf(handler.reply.data(), webkit_uri_scheme_request_get_path(request));
+            char* replyHTML = g_strdup_printf(handler.reply.data(), requestPath);
             g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(inputStream.get()), replyHTML, strlen(replyHTML), g_free);
         } else if (!g_strcmp0(scheme, "closed"))
             g_input_stream_close(inputStream.get(), 0, 0);
@@ -261,10 +280,55 @@
         webkit_web_context_register_uri_scheme(m_webContext.get(), scheme, uriSchemeRequestCallback, this, 0);
     }
 
+    virtual void loadCommitted() override
+    {
+        if (m_finishOnCommitted) {
+            GUniquePtr<GError> error(g_error_new_literal(g_quark_from_string(errorDomain), errorCode, afterInitialChunkErrorMessage));
+            webkit_uri_scheme_request_finish_error(m_uriSchemeRequest.get(), error.get());
+        }
+
+        LoadTrackingTest::loadCommitted();
+    }
+
+    void finishOnCommittedAndWaitUntilLoadFinished()
+    {
+        m_finishOnCommitted = true;
+        waitUntilLoadFinished();
+        m_finishOnCommitted = false;
+    }
+
     GRefPtr<WebKitURISchemeRequest> m_uriSchemeRequest;
     HashMap<String, URISchemeHandler> m_handlersMap;
+    bool m_finishOnCommitted { false };
 };
 
+String generateHTMLContent(unsigned contentLength)
+{
+    String baseString("abcdefghijklmnopqrstuvwxyz0123457890");
+    unsigned baseLength = baseString.length();
+
+    StringBuilder builder;
+    builder.append("<html><body>");
+
+    if (contentLength <= baseLength)
+        builder.append(baseString, 0, contentLength);
+    else {
+        unsigned currentLength = 0;
+        while (currentLength < contentLength) {
+            if ((currentLength + baseLength) <= contentLength)
+                builder.append(baseString);
+            else
+                builder.append(baseString, 0, contentLength - currentLength);
+
+            // Account for the 12 characters of the '<html><body>' prefix.
+            currentLength = builder.length() - 12;
+        }
+    }
+    builder.append("</body></html>");
+
+    return builder.toString();
+}
+
 static void testWebContextURIScheme(URISchemeTest* test, gconstpointer)
 {
     test->registerURISchemeHandler("foo", kBarHTML, strlen(kBarHTML), "text/html");
@@ -307,15 +371,36 @@
     g_assert(!test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
     g_assert(!test->m_loadEvents.contains(LoadTrackingTest::LoadFailed));
 
-    test->registerURISchemeHandler("error", 0, 0, 0);
+    // Anything over 8192 bytes will get multiple calls to g_input_stream_read_async in
+    // WebKitURISchemeRequest when reading data, but we still need way more than that to
+    // ensure that we reach the load-committed state before failing, so we use an 8MB HTML.
+    String longHTMLContent = generateHTMLContent(8 * 1024 * 1024);
+    test->registerURISchemeHandler("error", longHTMLContent.utf8().data(), -1, "text/html");
     test->m_loadEvents.clear();
     test->loadURI("error:error");
     test->waitUntilLoadFinished();
     g_assert(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
     g_assert(test->m_loadFailed);
     g_assert_error(test->m_error.get(), g_quark_from_string(errorDomain), errorCode);
-    g_assert_cmpstr(test->m_error->message, ==, errorMessage);
+    g_assert_cmpstr(test->m_error->message, ==, genericErrorMessage);
 
+    test->m_loadEvents.clear();
+    test->loadURI("error:before-response");
+    test->waitUntilLoadFinished();
+    g_assert(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
+    g_assert(test->m_loadFailed);
+    g_assert_error(test->m_error.get(), g_quark_from_string(errorDomain), errorCode);
+    g_assert_cmpstr(test->m_error->message, ==, beforeReceiveResponseErrorMessage);
+
+    test->m_loadEvents.clear();
+    test->loadURI("error:after-first-chunk");
+    test->finishOnCommittedAndWaitUntilLoadFinished();
+    g_assert(!test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
+    g_assert(test->m_loadEvents.contains(LoadTrackingTest::LoadFailed));
+    g_assert(test->m_loadFailed);
+    g_assert_error(test->m_error.get(), g_quark_from_string(errorDomain), errorCode);
+    g_assert_cmpstr(test->m_error->message, ==, afterInitialChunkErrorMessage);
+
     test->registerURISchemeHandler("closed", 0, 0, 0);
     test->m_loadEvents.clear();
     test->loadURI("closed:input-stream");
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to