Skip to content
Permalink
Browse files
[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.

Canonical link: https://commits.webkit.org/170205@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@193830 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
mariospr committed Dec 9, 2015
1 parent 2006eb7 commit 10c34ae4118aa1df1b991f82504e3b2e9b04c12e
@@ -1,3 +1,35 @@
2015-12-09 Mario Sanchez Prada <mario@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.choi@navercorp.com>

[CoordinatedGraphics][EFL] Fix unhandled web process message when launching MiniBrowser
@@ -116,10 +116,17 @@ void CustomProtocolManagerImpl::didFailWithError(uint64_t customProtocolID, cons
WebSoupRequestAsyncData* data = m_customProtocolMap.get(customProtocolID);
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::didLoadData(uint64_t customProtocolID, const IPC
void CustomProtocolManagerImpl::didReceiveResponse(uint64_t customProtocolID, const WebCore::ResourceResponse& response)
{
WebSoupRequestAsyncData* data = m_customProtocolMap.get(customProtocolID);
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));
@@ -166,6 +166,11 @@ static void webkitURISchemeRequestReadCallback(GInputStream* inputStream, GAsync
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 @@ void webkit_uri_scheme_request_finish_error(WebKitURISchemeRequest* request, GEr
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);
@@ -1294,6 +1294,11 @@ void webkitWebContextDidFinishLoadingCustomProtocol(WebKitWebContext* context, u
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);
@@ -42,6 +42,7 @@ WebKit::WebSoupCustomProtocolRequestManager* webkitWebContextGetRequestManager(W
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*);
@@ -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 @@ struct _WebKitSoupRequestInputStreamPrivate {
uint64_t bytesReceived;
uint64_t bytesRead;

GUniquePtr<GError> error;

Lock readLock;
std::unique_ptr<AsyncReadData> pendingAsyncRead;
};
@@ -92,6 +95,11 @@ static void webkitSoupRequestInputStreamReadAsync(GInputStream* inputStream, voi
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 @@ void webkitSoupRequestInputStreamAddData(WebKitSoupRequestInputStream* stream, c
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 = stream->priv->pendingAsyncRead.get();
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);
@@ -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 @@ struct _WebKitSoupRequestInputStreamClass {
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
@@ -1,3 +1,17 @@
2015-12-09 Mario Sanchez Prada <mario@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.choi@navercorp.com>

[EFL] Fix unhandled web process message when launching MiniBrowser
@@ -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,7 +198,10 @@ static const char* kBarHTML = "<html><body>Bar</body></html>";
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:
@@ -229,23 +233,38 @@ class URISchemeTest: public LoadTrackingTest {

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 @@ class URISchemeTest: public LoadTrackingTest {
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,14 +371,35 @@ static void testWebContextURIScheme(URISchemeTest* test, gconstpointer)
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();

0 comments on commit 10c34ae

Please sign in to comment.