summaryrefslogtreecommitdiffstats
path: root/netwerk/base/FuzzyLayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/base/FuzzyLayer.cpp407
1 files changed, 407 insertions, 0 deletions
diff --git a/netwerk/base/FuzzyLayer.cpp b/netwerk/base/FuzzyLayer.cpp
new file mode 100644
index 0000000000..8a9ee694dd
--- /dev/null
+++ b/netwerk/base/FuzzyLayer.cpp
@@ -0,0 +1,407 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FuzzyLayer.h"
+#include "nsTHashMap.h"
+#include "nsDeque.h"
+#include "nsIRunnable.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+
+#include "prmem.h"
+#include "prio.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gFuzzingLog("nsFuzzingNecko");
+
+#define FUZZING_LOG(args) \
+ MOZ_LOG(mozilla::net::gFuzzingLog, mozilla::LogLevel::Verbose, args)
+
+// Mutex for modifying our hash tables
+Mutex gConnRecvMutex("ConnectRecvMutex");
+
+// This structure will be created by addNetworkFuzzingBuffer below
+// and then added to the gNetworkFuzzingBuffers structure.
+//
+// It is assigned on connect and associated with the socket it belongs to.
+typedef struct {
+ const uint8_t* buf;
+ size_t size;
+ bool allowRead;
+ bool allowUnused;
+ PRNetAddr* addr;
+} NetworkFuzzingBuffer;
+
+// This holds all connections we have currently open.
+static nsTHashMap<nsPtrHashKey<PRFileDesc>, NetworkFuzzingBuffer*>
+ gConnectedNetworkFuzzingBuffers;
+
+// This holds all buffers for connections we can still open.
+static nsDeque<NetworkFuzzingBuffer> gNetworkFuzzingBuffers;
+
+// This is `true` once all connections are closed and either there are
+// no buffers left to be used or all remaining buffers are marked optional.
+// Used by `signalNetworkFuzzingDone` to tell the main thread if it needs
+// to spin-wait for `gFuzzingConnClosed`.
+static Atomic<bool> fuzzingNoWaitRequired(false);
+
+// Used to memorize if the main thread has indicated that it is done with
+// its iteration and we don't expect more connections now.
+static Atomic<bool> fuzzingMainSignaledDone(false);
+
+/*
+ * The flag `gFuzzingConnClosed` is set by `FuzzyClose` when all connections
+ * are closed *and* there are no more buffers in `gNetworkFuzzingBuffers` that
+ * must be used. The main thread spins until this becomes true to synchronize
+ * the fuzzing iteration between the main thread and the socket thread, if
+ * the prior call to `signalNetworkFuzzingDone` returned `false`.
+ */
+Atomic<bool> gFuzzingConnClosed(true);
+
+void addNetworkFuzzingBuffer(const uint8_t* data, size_t size, bool readFirst,
+ bool useIsOptional) {
+ if (size > INT32_MAX) {
+ MOZ_CRASH("Unsupported buffer size");
+ }
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* buf = new NetworkFuzzingBuffer();
+ buf->buf = data;
+ buf->size = size;
+ buf->allowRead = readFirst;
+ buf->allowUnused = useIsOptional;
+ buf->addr = nullptr;
+
+ gNetworkFuzzingBuffers.Push(buf);
+
+ fuzzingMainSignaledDone = false;
+ fuzzingNoWaitRequired = false;
+}
+
+/*
+ * This method should be called by fuzzing from the main thread to signal to
+ * the layer code that a fuzzing iteration is done. As a result, we can throw
+ * away any optional buffers and signal back once all connections have been
+ * closed. The main thread should synchronize on all connections being closed
+ * after the actual request/action is complete.
+ */
+bool signalNetworkFuzzingDone() {
+ FUZZING_LOG(("[signalNetworkFuzzingDone] Called."));
+ MutexAutoLock lock(gConnRecvMutex);
+ bool rv = false;
+
+ if (fuzzingNoWaitRequired) {
+ FUZZING_LOG(("[signalNetworkFuzzingDone] Purging remaining buffers."));
+ // Easy case, we already have no connections and non-optional buffers left.
+ gNetworkFuzzingBuffers.Erase();
+ gFuzzingConnClosed = true;
+ rv = true;
+ } else {
+ // We still have either connections left open or non-optional buffers left.
+ // In this case, FuzzyClose will handle the tear-down and signaling.
+ fuzzingMainSignaledDone = true;
+ }
+
+ return rv;
+}
+
+static PRDescIdentity sFuzzyLayerIdentity;
+static PRIOMethods sFuzzyLayerMethods;
+static PRIOMethods* sFuzzyLayerMethodsPtr = nullptr;
+
+static PRInt16 PR_CALLBACK FuzzyPoll(PRFileDesc* fd, PRInt16 in_flags,
+ PRInt16* out_flags) {
+ *out_flags = 0;
+
+ FUZZING_LOG(("[FuzzyPoll] Called with in_flags=%X.", in_flags));
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+
+ if (in_flags & PR_POLL_READ && fuzzBuf && fuzzBuf->allowRead) {
+ *out_flags = PR_POLL_READ;
+ return PR_POLL_READ;
+ }
+
+ if (in_flags & PR_POLL_WRITE) {
+ *out_flags = PR_POLL_WRITE;
+ return PR_POLL_WRITE;
+ }
+
+ return in_flags;
+}
+
+static PRStatus FuzzyConnect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront();
+ if (!buf) {
+ FUZZING_LOG(("[FuzzyConnect] Denying additional connection."));
+ return PR_FAILURE;
+ }
+
+ gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf);
+ fuzzingNoWaitRequired = false;
+
+ FUZZING_LOG(("[FuzzyConnect] Successfully opened connection: %p", fd));
+
+ gFuzzingConnClosed = false;
+
+ return PR_SUCCESS;
+}
+
+static PRInt32 FuzzySendTo(PRFileDesc* fd, const void* buf, PRInt32 amount,
+ PRIntn flags, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.PopFront();
+ if (!buf) {
+ FUZZING_LOG(("[FuzzySentTo] Denying additional connection."));
+ return 0;
+ }
+
+ gConnectedNetworkFuzzingBuffers.InsertOrUpdate(fd, buf);
+
+ // Store connection address
+ buf->addr = new PRNetAddr;
+ memcpy(buf->addr, addr, sizeof(PRNetAddr));
+
+ fuzzingNoWaitRequired = false;
+
+ FUZZING_LOG(("[FuzzySendTo] Successfully opened connection: %p", fd));
+
+ gFuzzingConnClosed = false;
+ }
+
+ // Allow reading once the implementation has written at least some data
+ if (fuzzBuf && !fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzySendTo] Write received, allowing further reads."));
+ fuzzBuf->allowRead = true;
+ }
+
+ FUZZING_LOG(("[FuzzySendTo] Sent %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzySend(PRFileDesc* fd, const void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzySend] Write on socket that is not connected."));
+ amount = 0;
+ }
+
+ // Allow reading once the implementation has written at least some data
+ if (fuzzBuf && !fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzySend] Write received, allowing further reads."));
+ fuzzBuf->allowRead = true;
+ }
+
+ FUZZING_LOG(("[FuzzySend] Sent %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzyWrite(PRFileDesc* fd, const void* buf, PRInt32 amount) {
+ return FuzzySend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRInt32 FuzzyRecv(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sFuzzyLayerIdentity);
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, connection is closed."));
+ return 0;
+ }
+
+ // As long as we haven't written anything, act as if no data was there yet
+ if (!fuzzBuf->allowRead) {
+ FUZZING_LOG(("[FuzzyRecv] Denying read, nothing written before."));
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ 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 (!fuzzBuf->size) {
+ FUZZING_LOG(("[FuzzyRecv] Read failed, no data left."));
+ return 0;
+ }
+
+ // Use the remains of fuzzing buffer, if too little is left
+ if (fuzzBuf->size < (PRUint32)amount) amount = fuzzBuf->size;
+
+ // Consume buffer, copy data
+ memcpy(buf, fuzzBuf->buf, amount);
+
+ if (!(flags & PR_MSG_PEEK)) {
+ fuzzBuf->buf += amount;
+ fuzzBuf->size -= amount;
+ }
+
+ FUZZING_LOG(("[FuzzyRecv] Read %" PRIx32 " bytes of data.", amount));
+
+ return amount;
+}
+
+static PRInt32 FuzzyRecvFrom(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ // Return the same address used for initial SendTo on this fd
+ if (addr) {
+ NetworkFuzzingBuffer* fuzzBuf = gConnectedNetworkFuzzingBuffers.Get(fd);
+ if (!fuzzBuf) {
+ FUZZING_LOG(("[FuzzyRecvFrom] Denying read, connection is closed."));
+ return 0;
+ }
+
+ if (fuzzBuf->addr) {
+ memcpy(addr, fuzzBuf->addr, sizeof(PRNetAddr));
+ } else {
+ FUZZING_LOG(("[FuzzyRecvFrom] No address found for connection"));
+ }
+ }
+ return FuzzyRecv(fd, buf, amount, flags, timeout);
+}
+
+static PRInt32 FuzzyRead(PRFileDesc* fd, void* buf, PRInt32 amount) {
+ return FuzzyRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRStatus FuzzyClose(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+ MOZ_RELEASE_ASSERT(layer && layer->identity == sFuzzyLayerIdentity,
+ "Fuzzy Layer not on top of stack");
+
+ layer->dtor(layer);
+
+ MutexAutoLock lock(gConnRecvMutex);
+
+ NetworkFuzzingBuffer* fuzzBuf = nullptr;
+ if (gConnectedNetworkFuzzingBuffers.Remove(fd, &fuzzBuf)) {
+ FUZZING_LOG(("[FuzzyClose] Received close on socket %p", fd));
+ if (fuzzBuf->addr) {
+ delete fuzzBuf->addr;
+ }
+ delete fuzzBuf;
+ } else {
+ /* Happens when close is called on a non-connected socket */
+ FUZZING_LOG(("[FuzzyClose] Received close on unknown socket %p.", fd));
+ }
+
+ PRStatus ret = fd->methods->close(fd);
+
+ if (!gConnectedNetworkFuzzingBuffers.Count()) {
+ // At this point, all connections are closed, but we might still have
+ // unused network buffers that were not marked as optional.
+ bool haveRemainingUnusedBuffers = false;
+ for (size_t i = 0; i < gNetworkFuzzingBuffers.GetSize(); ++i) {
+ NetworkFuzzingBuffer* buf = gNetworkFuzzingBuffers.ObjectAt(i);
+
+ if (!buf->allowUnused) {
+ haveRemainingUnusedBuffers = true;
+ break;
+ }
+ }
+
+ if (haveRemainingUnusedBuffers) {
+ FUZZING_LOG(
+ ("[FuzzyClose] All connections closed, waiting for remaining "
+ "connections."));
+ } else if (!fuzzingMainSignaledDone) {
+ // We have no connections left, but the main thread hasn't signaled us
+ // yet. For now, that means once the main thread signals us, we can tell
+ // it immediately that it won't have to wait for closing connections.
+ FUZZING_LOG(
+ ("[FuzzyClose] All connections closed, waiting for main thread."));
+ fuzzingNoWaitRequired = true;
+ } else {
+ // No connections left and main thread is already done. Perform cleanup
+ // and then signal the main thread to continue.
+ FUZZING_LOG(("[FuzzyClose] All connections closed, cleaning up."));
+
+ gNetworkFuzzingBuffers.Erase();
+ gFuzzingConnClosed = true;
+
+ // We need to dispatch this so the main thread is guaranteed to wake up
+ nsCOMPtr<nsIRunnable> r(new mozilla::Runnable("Dummy"));
+ NS_DispatchToMainThread(r.forget());
+ }
+ } else {
+ FUZZING_LOG(("[FuzzyClose] Connection closed."));
+ }
+
+ return ret;
+}
+
+nsresult AttachFuzzyIOLayer(PRFileDesc* fd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!sFuzzyLayerMethodsPtr) {
+ sFuzzyLayerIdentity = PR_GetUniqueIdentity("Fuzzy Layer");
+ sFuzzyLayerMethods = *PR_GetDefaultIOMethods();
+ sFuzzyLayerMethods.connect = FuzzyConnect;
+ sFuzzyLayerMethods.send = FuzzySend;
+ sFuzzyLayerMethods.sendto = FuzzySendTo;
+ sFuzzyLayerMethods.write = FuzzyWrite;
+ sFuzzyLayerMethods.recv = FuzzyRecv;
+ sFuzzyLayerMethods.recvfrom = FuzzyRecvFrom;
+ sFuzzyLayerMethods.read = FuzzyRead;
+ sFuzzyLayerMethods.close = FuzzyClose;
+ sFuzzyLayerMethods.poll = FuzzyPoll;
+ sFuzzyLayerMethodsPtr = &sFuzzyLayerMethods;
+ }
+
+ PRFileDesc* layer =
+ PR_CreateIOLayerStub(sFuzzyLayerIdentity, sFuzzyLayerMethodsPtr);
+
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRStatus status = PR_PushIOLayer(fd, PR_TOP_IO_LAYER, layer);
+
+ if (status == PR_FAILURE) {
+ PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla