diff --git a/netwerk/base/FuzzyLayer.cpp b/netwerk/base/FuzzyLayer.cpp index 1d27f6c0de29..c8b718e2de70 100644 --- a/netwerk/base/FuzzyLayer.cpp +++ b/netwerk/base/FuzzyLayer.cpp @@ -121,6 +121,11 @@ static PRInt32 FuzzyRecv(PRFileDesc* fd, void* buf, PRInt32 amount, return -1; } + if (gFuzzingConnClosed) { + FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed.")); + return 0; + } + // No data left, act as if the connection was closed. if (!gFuzzingSize) return 0; diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index 8e61f6f8763b..a1923e135df3 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -3795,8 +3795,14 @@ WebSocketChannel::OnStartRequest(nsIRequest* aRequest) { "HTTP response header Sec-WebSocket-Accept check failed\n")); LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n", mHashedSecret.get(), respAccept.get())); - AbortSession(NS_ERROR_ILLEGAL_VALUE); - return NS_ERROR_ILLEGAL_VALUE; +#ifdef FUZZING + if (NS_FAILED(rv) || respAccept.IsEmpty()) { +#endif + AbortSession(NS_ERROR_ILLEGAL_VALUE); + return NS_ERROR_ILLEGAL_VALUE; +#ifdef FUZZING + } +#endif } } diff --git a/netwerk/protocol/websocket/moz.build b/netwerk/protocol/websocket/moz.build index 28ffb6bd9796..f74a4430953f 100644 --- a/netwerk/protocol/websocket/moz.build +++ b/netwerk/protocol/websocket/moz.build @@ -57,3 +57,5 @@ LOCAL_INCLUDES += [ if CONFIG['CC_TYPE'] in ('clang', 'gcc'): CXXFLAGS += ['-Wno-error=shadow'] + +include('/tools/fuzzing/libfuzzer-config.mozbuild') diff --git a/netwerk/test/fuzz/TestWebsocketFuzzing.cpp b/netwerk/test/fuzz/TestWebsocketFuzzing.cpp new file mode 100644 index 000000000000..bbde94d27be0 --- /dev/null +++ b/netwerk/test/fuzz/TestWebsocketFuzzing.cpp @@ -0,0 +1,217 @@ +#include "mozilla/Preferences.h" + +#include "FuzzingInterface.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsCycleCollector.h" +#include "nsIPrincipal.h" +#include "nsIWebSocketChannel.h" +#include "nsIWebSocketListener.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "nsScriptSecurityManager.h" +#include "nsServiceManagerUtils.h" +#include "NullPrincipal.h" + +namespace mozilla { +namespace net { + +// Used to determine if the fuzzing target should use https:// in spec. +static bool fuzzWSS = true; + +class FuzzingWebSocketListener final : public nsIWebSocketListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBSOCKETLISTENER + + FuzzingWebSocketListener() = default; + + void waitUntilDoneOrStarted() { + SpinEventLoopUntil([&]() { return mChannelDone || mChannelStarted; }); + } + + void waitUntilDone() { + SpinEventLoopUntil([&]() { return mChannelDone; }); + } + + void waitUntilDoneOrAck() { + SpinEventLoopUntil([&]() { return mChannelDone || mChannelAck; }); + } + + bool isStarted() { return mChannelStarted; } + + private: + ~FuzzingWebSocketListener() = default; + bool mChannelDone = false; + bool mChannelStarted = false; + bool mChannelAck = false; +}; + +NS_IMPL_ISUPPORTS(FuzzingWebSocketListener, nsIWebSocketListener) + +NS_IMETHODIMP +FuzzingWebSocketListener::OnStart(nsISupports* aContext) { + FUZZING_LOG(("FuzzingWebSocketListener::OnStart")); + mChannelStarted = true; + return NS_OK; +} + +NS_IMETHODIMP +FuzzingWebSocketListener::OnStop(nsISupports* aContext, nsresult aStatusCode) { + FUZZING_LOG(("FuzzingWebSocketListener::OnStop")); + mChannelDone = true; + return NS_OK; +} + +NS_IMETHODIMP +FuzzingWebSocketListener::OnAcknowledge(nsISupports* aContext, uint32_t aSize) { + FUZZING_LOG(("FuzzingWebSocketListener::OnAcknowledge")); + mChannelAck = true; + return NS_OK; +} + +NS_IMETHODIMP +FuzzingWebSocketListener::OnServerClose(nsISupports* aContext, uint16_t aCode, + const nsACString& aReason) { + FUZZING_LOG(("FuzzingWebSocketListener::OnServerClose")); + return NS_OK; +} + +NS_IMETHODIMP +FuzzingWebSocketListener::OnMessageAvailable(nsISupports* aContext, + const nsACString& aMsg) { + FUZZING_LOG(("FuzzingWebSocketListener::OnMessageAvailable")); + return NS_OK; +} + +NS_IMETHODIMP +FuzzingWebSocketListener::OnBinaryMessageAvailable(nsISupports* aContext, + const nsACString& aMsg) { + FUZZING_LOG(("FuzzingWebSocketListener::OnBinaryMessageAvailable")); + return NS_OK; +} + +// Forward declaration to the function in FuzzyLayer.cpp, +// used to set the buffer to the global defined there. +void setNetworkFuzzingBuffer(const uint8_t* data, size_t size); +extern Atomic gFuzzingConnClosed; + +static int FuzzingInitNetworkWebsocket(int* argc, char*** argv) { + Preferences::SetBool("network.dns.native-is-localhost", true); + Preferences::SetBool("fuzzing.necko.enabled", true); + Preferences::SetBool("network.websocket.delay-failed-reconnects", false); + Preferences::SetInt("network.http.speculative-parallel-limit", 0); + return 0; +} + +static int FuzzingInitNetworkWebsocketPlain(int* argc, char*** argv) { + fuzzWSS = false; + return FuzzingInitNetworkWebsocket(argc, argv); +} + +static int FuzzingRunNetworkWebsocket(const uint8_t* data, size_t size) { + // Set the data to be processed + setNetworkFuzzingBuffer(data, size); + + nsWeakPtr channelRef; + + { + nsresult rv; + + nsSecurityFlags secFlags; + secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL | + nsILoadInfo::SEC_SANDBOXED; + + nsCOMPtr url; + nsAutoCString spec; + RefPtr gWebSocketListener; + nsCOMPtr gWebSocketChannel; + + if (fuzzWSS) { + spec = "https://127.0.0.1/"; + gWebSocketChannel = + do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv); + } else { + spec = "http://127.0.0.1/"; + gWebSocketChannel = + do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv); + } + + if (rv != NS_OK) { + MOZ_CRASH("Failed to create WebSocketChannel"); + } + + if (NS_NewURI(getter_AddRefs(url), spec) != NS_OK) { + MOZ_CRASH("Call to NS_NewURI failed."); + } + + nsCOMPtr nullPrincipal = + NullPrincipal::CreateWithoutOriginAttributes(); + + rv = gWebSocketChannel->InitLoadInfo(nullptr, nullPrincipal, + nsContentUtils::GetSystemPrincipal(), + secFlags, nsIContentPolicy::TYPE_WEBSOCKET); + + if (rv != NS_OK) { + MOZ_CRASH("Failed to call InitLoadInfo"); + } + + gWebSocketListener = new FuzzingWebSocketListener(); + + rv = + gWebSocketChannel->AsyncOpen(url, spec, 0, gWebSocketListener, nullptr); + + if (rv == NS_OK) { + FUZZING_LOG(("Successful call to AsyncOpen")); + + // Wait for StartRequest or StopRequest + gWebSocketListener->waitUntilDoneOrStarted(); + + if (gWebSocketListener->isStarted()) { + rv = + gWebSocketChannel->SendBinaryMsg(NS_LITERAL_CSTRING("Hello world")); + + if (rv != NS_OK) { + FUZZING_LOG(("Warning: Failed to call SendBinaryMsg")); + } else { + gWebSocketListener->waitUntilDoneOrAck(); + } + + rv = gWebSocketChannel->Close(1000, NS_LITERAL_CSTRING("")); + + if (rv != NS_OK) { + FUZZING_LOG(("Warning: Failed to call close")); + } + } + + // Wait for StopRequest + gWebSocketListener->waitUntilDone(); + } else { + FUZZING_LOG(("Warning: Failed to call AsyncOpen")); + } + + channelRef = do_GetWeakReference(gWebSocketChannel); + } + + // Wait for the channel to be destroyed + SpinEventLoopUntil([&]() -> bool { + nsCycleCollector_collect(nullptr); + nsCOMPtr channel = do_QueryReferent(channelRef); + return channel == nullptr; + }); + + // Wait for the connection to indicate closed + SpinEventLoopUntil([&]() -> bool { return gFuzzingConnClosed; }); + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkWebsocket, + FuzzingRunNetworkWebsocket, NetworkWebsocket); +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkWebsocketPlain, + FuzzingRunNetworkWebsocket, NetworkWebsocketPlain); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/test/fuzz/moz.build b/netwerk/test/fuzz/moz.build index ad941da52881..f3d2d1752ab9 100644 --- a/netwerk/test/fuzz/moz.build +++ b/netwerk/test/fuzz/moz.build @@ -6,6 +6,7 @@ UNIFIED_SOURCES += [ 'TestHttpFuzzing.cpp', + 'TestWebsocketFuzzing.cpp', ] LOCAL_INCLUDES += [