summaryrefslogtreecommitdiffstats
path: root/tools/fuzzing/ipc
diff options
context:
space:
mode:
Diffstat (limited to 'tools/fuzzing/ipc')
-rw-r--r--tools/fuzzing/ipc/IPCFuzzController.cpp1328
-rw-r--r--tools/fuzzing/ipc/IPCFuzzController.h216
-rw-r--r--tools/fuzzing/ipc/moz.build27
3 files changed, 1571 insertions, 0 deletions
diff --git a/tools/fuzzing/ipc/IPCFuzzController.cpp b/tools/fuzzing/ipc/IPCFuzzController.cpp
new file mode 100644
index 0000000000..54856e9f2c
--- /dev/null
+++ b/tools/fuzzing/ipc/IPCFuzzController.cpp
@@ -0,0 +1,1328 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "IPCFuzzController.h"
+#include "mozilla/Fuzzing.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/SyncRunnable.h"
+
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/MessageLink.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/NodeChannel.h"
+#include "mozilla/ipc/NodeController.h"
+
+#include "mozilla/ipc/PIdleScheduler.h"
+#include "mozilla/ipc/PBackground.h"
+#include "mozilla/dom/PContent.h"
+
+#include <fstream>
+#include <sstream>
+#include <algorithm>
+
+using namespace mojo::core::ports;
+using namespace mozilla::ipc;
+
+// Sync inject means that the actual fuzzing takes place on the I/O thread
+// and hence it injects directly into the target NodeChannel. In async mode,
+// we run the fuzzing on a separate thread and dispatch the runnable that
+// injects the message back to the I/O thread. Both approaches seem to work
+// and have advantages and disadvantages. Blocking the I/O thread means no
+// IPC between other processes will interfere with our fuzzing in the meantime
+// but blocking could also cause hangs when such IPC is required during the
+// fuzzing runtime for some reason.
+// #define MOZ_FUZZ_IPC_SYNC_INJECT 1
+
+// Synchronize after each message rather than just after every constructor
+// or at the end of the iteration. Doing so costs us some performance because
+// we have to wait for each packet and process events on the main thread,
+// but it is necessary when using `OnMessageError` to release on early errors.
+#define MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG 1
+
+namespace mozilla {
+namespace fuzzing {
+
+const uint32_t ipcDefaultTriggerMsg = dom::PContent::Msg_SignalFuzzingReady__ID;
+
+IPCFuzzController::IPCFuzzController()
+ : useLastPortName(false),
+ useLastActor(0),
+ mMutex("IPCFuzzController"),
+ mIPCTriggerMsg(ipcDefaultTriggerMsg) {
+ InitializeIPCTypes();
+
+ // We use 6 bits for port index selection without wrapping, so we just
+ // create 64 empty rows in our port matrix. Not all of these rows will
+ // be used though.
+ portNames.resize(64);
+
+ // This is our port / toplevel actor ordering. Add new toplevel actors
+ // here to support them in the fuzzer. Do *NOT* change the order of
+ // these, as it will invalidate our fuzzing corpus.
+ portNameToIndex["PContent"] = 0;
+ portNameToIndex["PBackground"] = 1;
+ portNameToIndex["PBackgroundStarter"] = 2;
+ portNameToIndex["PCompositorManager"] = 3;
+ portNameToIndex["PImageBridge"] = 4;
+ portNameToIndex["PProcessHangMonitor"] = 5;
+ portNameToIndex["PProfiler"] = 6;
+ portNameToIndex["PVRManager"] = 7;
+ portNameToIndex["PCanvasManager"] = 8;
+
+ // Used to select the n-th trigger message as a starting point for fuzzing
+ // in single message mode. A value of 1 will skip the first matching message
+ // and start fuzzing on the second message, and so on.
+ if (!!getenv("MOZ_FUZZ_IPC_TRIGGER_SINGLEMSG_WAIT")) {
+ mIPCTriggerSingleMsgWait =
+ atoi(getenv("MOZ_FUZZ_IPC_TRIGGER_SINGLEMSG_WAIT"));
+ }
+
+ // When set, dump all IPC message at or above the specified size to files.
+ // Useful to collect samples of different types in one run.
+ if (!!getenv("MOZ_FUZZ_IPC_DUMP_ALL_MSGS_SIZE")) {
+ mIPCDumpAllMsgsSize.emplace(
+ atoi(getenv("MOZ_FUZZ_IPC_DUMP_ALL_MSGS_SIZE")));
+ }
+}
+
+// static
+IPCFuzzController& IPCFuzzController::instance() {
+ static IPCFuzzController ifc;
+ return ifc;
+}
+
+void IPCFuzzController::InitializeIPCTypes() {
+ const char* cons = "Constructor";
+ size_t cons_len = strlen(cons);
+
+ const char* targetNameTrigger = getenv("MOZ_FUZZ_IPC_TRIGGER");
+ const char* targetNameDump = getenv("MOZ_FUZZ_IPC_DUMPMSG");
+
+ for (uint32_t start = 0; start < LastMsgIndex; ++start) {
+ uint32_t i;
+ for (i = (start << 16) + 1; i < ((start + 1) << 16); ++i) {
+ const char* name = IPC::StringFromIPCMessageType(i);
+
+ if (name[0] == '<') break;
+
+ if (targetNameTrigger && !strcmp(name, targetNameTrigger)) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [InitializeIPCTypes] Located trigger message (%s, %d)\n",
+ targetNameTrigger, i);
+ mIPCTriggerMsg = i;
+ }
+
+ if (targetNameDump && !strcmp(name, targetNameDump)) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [InitializeIPCTypes] Located dump message (%s, %d)\n",
+ targetNameDump, i);
+ mIPCDumpMsg.emplace(i);
+ }
+
+ size_t len = strlen(name);
+ if (len > cons_len && !memcmp(cons, name + len - cons_len, cons_len)) {
+ constructorTypes.insert(i);
+ }
+ }
+
+ uint32_t msgCount = i - ((start << 16) + 1);
+ if (msgCount) {
+ validMsgTypes[(ProtocolId)start] = msgCount;
+ }
+ }
+}
+
+bool IPCFuzzController::GetRandomIPCMessageType(ProtocolId pId,
+ uint16_t typeOffset,
+ uint32_t* type) {
+ auto pIdEntry = validMsgTypes.find(pId);
+ if (pIdEntry == validMsgTypes.end()) {
+ return false;
+ }
+
+ *type =
+ ((uint32_t)pIdEntry->first << 16) + 1 + (typeOffset % pIdEntry->second);
+
+ if (strstr(IPC::StringFromIPCMessageType(*type), "::Reply_")) {
+ *type = *type - 1;
+ }
+
+ return true;
+}
+
+void IPCFuzzController::OnActorConnected(IProtocol* protocol) {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: IPCFuzzController::OnActorConnected() Mutex try\n");
+
+ // Called on background threads and modifies `actorIds`.
+ MutexAutoLock lock(mMutex);
+
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: IPCFuzzController::OnActorConnected() Mutex locked\n");
+
+ static bool protoIdFilterInitialized = false;
+ static bool allowNewActors = false;
+ static std::string protoIdFilter;
+ if (!protoIdFilterInitialized) {
+ const char* protoIdFilterStr = getenv("MOZ_FUZZ_PROTOID_FILTER");
+ if (protoIdFilterStr) {
+ protoIdFilter = std::string(protoIdFilterStr);
+ }
+ protoIdFilterInitialized = true;
+ }
+
+#ifdef FUZZ_DEBUG
+ MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %d Protocol: %s\n",
+ protocol->Id(), protocol->GetProtocolName());
+#endif
+
+ MessageChannel* channel = protocol->ToplevelProtocol()->GetIPCChannel();
+
+ Maybe<PortName> portName = channel->GetPortName();
+ if (portName) {
+ if (!protoIdFilter.empty() &&
+ (!Nyx::instance().started() || !allowNewActors) &&
+ strcmp(protocol->GetProtocolName(), protoIdFilter.c_str()) &&
+ !actorIds[*portName].empty()) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [OnActorConnected] ActorID %d Protocol: %s ignored due to "
+ "filter.\n",
+ protocol->Id(), protocol->GetProtocolName());
+ return;
+ } else if (!protoIdFilter.empty() &&
+ !strcmp(protocol->GetProtocolName(), protoIdFilter.c_str())) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [OnActorConnected] ActorID %d Protocol: %s matches target.\n",
+ protocol->Id(), protocol->GetProtocolName());
+ } else if (!protoIdFilter.empty() && actorIds[*portName].empty()) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [OnActorConnected] ActorID %d Protocol: %s is toplevel "
+ "actor.\n",
+ protocol->Id(), protocol->GetProtocolName());
+ }
+
+ actorIds[*portName].emplace_back(protocol->Id(), protocol->GetProtocolId());
+
+ if (Nyx::instance().started() && protoIdFilter.empty()) {
+ // Fix the port we will be using for at least the next 5 messages
+ useLastPortName = true;
+ lastActorPortName = *portName;
+
+ // Use this actor for the next 5 messages
+ useLastActor = 5;
+ }
+ } else {
+ MOZ_FUZZING_NYX_DEBUG("WARNING: No port name on actor?!\n");
+ }
+}
+
+void IPCFuzzController::OnActorDestroyed(IProtocol* protocol) {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+#ifdef FUZZ_DEBUG
+ MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorDestroyed] ActorID %d Protocol: %s\n",
+ protocol->Id(), protocol->GetProtocolName());
+#endif
+
+ MessageChannel* channel = protocol->ToplevelProtocol()->GetIPCChannel();
+
+ Maybe<PortName> portName = channel->GetPortName();
+ if (portName) {
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: IPCFuzzController::OnActorDestroyed() Mutex try\n");
+ // Called on background threads and modifies `actorIds`.
+ MutexAutoLock lock(mMutex);
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: IPCFuzzController::OnActorDestroyed() Mutex locked\n");
+
+ for (auto iter = actorIds[*portName].begin();
+ iter != actorIds[*portName].end();) {
+ if (iter->first == protocol->Id() &&
+ iter->second == protocol->GetProtocolId()) {
+ iter = actorIds[*portName].erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+ } else {
+ MOZ_FUZZING_NYX_DEBUG("WARNING: No port name on destroyed actor?!\n");
+ }
+}
+
+void IPCFuzzController::AddToplevelActor(PortName name, ProtocolId protocolId) {
+ const char* protocolName = ProtocolIdToName(protocolId);
+ auto result = portNameToIndex.find(protocolName);
+ if (result == portNameToIndex.end()) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "ERROR: [OnActorConnected] Unknown Top-Level Protocol: %s\n",
+ protocolName);
+ MOZ_FUZZING_NYX_ABORT("Unknown Top-Level Protocol\n");
+ }
+ uint8_t portIndex = result->second;
+ portNames[portIndex].push_back(name);
+ portNameToProtocolName[name] = std::string(protocolName);
+}
+
+bool IPCFuzzController::ObserveIPCMessage(mozilla::ipc::NodeChannel* channel,
+ IPC::Message& aMessage) {
+ if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_Generic")) {
+ // Fuzzer is not enabled.
+ return true;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // For now we only care about things in the parent process.
+ return true;
+ }
+
+ if (aMessage.IsFuzzMsg()) {
+ // Don't observe our own messages. If this is the first fuzzing message,
+ // we also block further non-fuzzing communication on that node.
+ if (!channel->mBlockSendRecv) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [NodeChannel::OnMessageReceived] Blocking further "
+ "communication on Port %lu %lu (seen fuzz msg)\n",
+ channel->GetName().v1, channel->GetName().v2);
+ channel->mBlockSendRecv = true;
+ }
+ return true;
+ } else if (aMessage.type() == mIPCTriggerMsg && !Nyx::instance().started()) {
+ MOZ_FUZZING_NYX_PRINT("DEBUG: Ready message detected.\n");
+
+ if (!haveTargetNodeName && !!getenv("MOZ_FUZZ_PROTOID_FILTER")) {
+ // With a protocol filter set, we want to pin to the actor that
+ // received the ready message and stay there. We should do this here
+ // because OnActorConnected can be called even after the ready message
+ // has been received and potentially override the correct actor.
+
+ // Get the port name associated with this message
+ Vector<char, 256, InfallibleAllocPolicy> footer;
+ if (!footer.initLengthUninitialized(aMessage.event_footer_size()) ||
+ !aMessage.ReadFooter(footer.begin(), footer.length(), false)) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: Failed to read message footer.\n");
+ }
+
+ UniquePtr<Event> event =
+ Event::Deserialize(footer.begin(), footer.length());
+
+ if (!event || event->type() != Event::kUserMessage) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: Trigger message is not kUserMessage?!\n");
+ }
+
+ lastActorPortName = event->port_name();
+ useLastPortName = true;
+ useLastActor = 1024;
+ }
+
+ // TODO: This is specific to PContent fuzzing. If we later want to fuzz
+ // a different process pair, we need additional signals here.
+ OnChildReady();
+
+ // The ready message indicates the right node name for us to work with
+ // and we should only ever receive it once.
+ if (!haveTargetNodeName) {
+ targetNodeName = channel->GetName();
+ haveTargetNodeName = true;
+
+ // We can also use this message as the base template for other messages
+ if (!this->sampleHeader.initLengthUninitialized(
+ sizeof(IPC::Message::Header))) {
+ MOZ_FUZZING_NYX_ABORT("sampleHeader.initLengthUninitialized failed\n");
+ }
+
+ memcpy(sampleHeader.begin(), aMessage.header(),
+ sizeof(IPC::Message::Header));
+ }
+ } else if (haveTargetNodeName && targetNodeName != channel->GetName()) {
+ // Not our node, no need to observe
+ return true;
+ } else if (Nyx::instance().started()) {
+ // When fuzzing is already started, we shouldn't observe messages anymore.
+ if (!channel->mBlockSendRecv) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [NodeChannel::OnMessageReceived] Blocking further "
+ "communication on Port %lu %lu (fuzzing started)\n",
+ channel->GetName().v1, channel->GetName().v2);
+ channel->mBlockSendRecv = true;
+ }
+ return false;
+ }
+
+ Vector<char, 256, InfallibleAllocPolicy> footer;
+
+ if (!footer.initLengthUninitialized(aMessage.event_footer_size())) {
+ MOZ_FUZZING_NYX_ABORT("footer.initLengthUninitialized failed\n");
+ }
+
+ if (!aMessage.ReadFooter(footer.begin(), footer.length(), false)) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: ReadFooter() failed?!\n");
+ }
+
+ UniquePtr<Event> event = Event::Deserialize(footer.begin(), footer.length());
+
+ if (!event) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: Failed to deserialize observed message?!\n");
+ }
+
+ if (event->type() == Event::kUserMessage) {
+ if (haveTargetNodeName && !fuzzingStartPending) {
+ bool missingActor = false;
+
+ // Check if we have any entries in our port map that we haven't seen yet
+ // though `OnActorConnected`. That method is called on a background
+ // thread and this call will race with the I/O thread.
+ //
+ // However, with a custom MOZ_FUZZ_IPC_TRIGGER we assume we want to keep
+ // the port pinned so we don't have to wait at all.
+ if (mIPCTriggerMsg == ipcDefaultTriggerMsg) {
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: IPCFuzzController::ObserveIPCMessage() Mutex try\n");
+ // Called on the I/O thread and reads `portSeqNos`.
+ //
+ // IMPORTANT: We must give up any locks before entering `StartFuzzing`,
+ // as we will never return. This would cause a deadlock with new actors
+ // being created and `OnActorConnected` being called.
+ MutexAutoLock lock(mMutex);
+
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: IPCFuzzController::ObserveIPCMessage() Mutex locked\n");
+
+ for (auto iter = portSeqNos.begin(); iter != portSeqNos.end(); ++iter) {
+ auto result = actorIds.find(iter->first);
+ if (result == actorIds.end()) {
+ // Make sure we only wait for actors that belong to us.
+ auto result = portNodeName.find(iter->first);
+ if (result->second == targetNodeName) {
+ missingActor = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (missingActor) {
+ MOZ_FUZZING_NYX_PRINT(
+ "INFO: Delaying fuzzing start, missing actors...\n");
+ } else if (!childReady) {
+ MOZ_FUZZING_NYX_PRINT(
+ "INFO: Delaying fuzzing start, waiting for child...\n");
+ } else {
+ fuzzingStartPending = true;
+ StartFuzzing(channel, aMessage);
+
+ // In the async case, we return and can already block the relevant
+ // communication.
+ if (targetNodeName == channel->GetName()) {
+ if (!channel->mBlockSendRecv) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: [NodeChannel::OnMessageReceived] Blocking further "
+ "communication on Port %lu %lu (fuzzing start pending)\n",
+ channel->GetName().v1, channel->GetName().v2);
+ channel->mBlockSendRecv = true;
+ }
+
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // Add/update sequence numbers. We need to make sure to do this after our
+ // call to `StartFuzzing` because once we start fuzzing, the message will
+ // never actually be processed, so we run into a sequence number desync.
+ {
+ // Get the port name associated with this message
+ UserMessageEvent* userMsgEv = static_cast<UserMessageEvent*>(event.get());
+ PortName name = event->port_name();
+
+ // Called on the I/O thread and modifies `portSeqNos`.
+ MutexAutoLock lock(mMutex);
+ portSeqNos.insert_or_assign(
+ name, std::pair<int32_t, uint64_t>(aMessage.seqno(),
+ userMsgEv->sequence_num()));
+
+ portNodeName.insert_or_assign(name, channel->GetName());
+ }
+ }
+
+ return true;
+}
+
+void IPCFuzzController::OnMessageError(
+ mozilla::ipc::HasResultCodes::Result code, const IPC::Message& aMsg) {
+ if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_Generic")) {
+ // Fuzzer is not enabled.
+ return;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // For now we only care about things in the parent process.
+ return;
+ }
+
+ if (!aMsg.IsFuzzMsg()) {
+ // We should only act upon fuzzing messages.
+ return;
+ }
+
+ switch (code) {
+ case ipc::HasResultCodes::MsgNotKnown:
+ // Seeing this error should be rare - one potential reason is if a sync
+ // message is sent as async and vice versa. Other than that, we shouldn't
+ // be generating this error at all.
+ Nyx::instance().handle_event("MOZ_IPC_UNKNOWN_TYPE", nullptr, 0, nullptr);
+#ifdef FUZZ_DEBUG
+ MOZ_FUZZING_NYX_PRINTF(
+ "WARNING: MOZ_IPC_UNKNOWN_TYPE for message type %s (%u) routed to "
+ "actor %d (sync %d)\n",
+ IPC::StringFromIPCMessageType(aMsg.type()), aMsg.type(),
+ aMsg.routing_id(), aMsg.is_sync());
+#endif
+ break;
+ case ipc::HasResultCodes::MsgNotAllowed:
+ Nyx::instance().handle_event("MOZ_IPC_NOTALLOWED_ERROR", nullptr, 0,
+ nullptr);
+ break;
+ case ipc::HasResultCodes::MsgPayloadError:
+ case ipc::HasResultCodes::MsgValueError:
+ Nyx::instance().handle_event("MOZ_IPC_DESERIALIZE_ERROR", nullptr, 0,
+ nullptr);
+ break;
+ case ipc::HasResultCodes::MsgProcessingError:
+ Nyx::instance().handle_event("MOZ_IPC_PROCESS_ERROR", nullptr, 0,
+ nullptr);
+ break;
+ case ipc::HasResultCodes::MsgRouteError:
+ Nyx::instance().handle_event("MOZ_IPC_ROUTE_ERROR", nullptr, 0, nullptr);
+ break;
+ default:
+ MOZ_FUZZING_NYX_ABORT("unknown Result code");
+ }
+
+ // Count this message as one iteration as well.
+ Nyx::instance().release(IPCFuzzController::instance().getMessageStopCount() +
+ 1);
+}
+
+bool IPCFuzzController::MakeTargetDecision(
+ uint8_t portIndex, uint8_t portInstanceIndex, uint8_t actorIndex,
+ uint16_t typeOffset, PortName* name, int32_t* seqno, uint64_t* fseqno,
+ int32_t* actorId, uint32_t* type, bool* is_cons, bool update) {
+ // Every possible toplevel actor type has a fixed number that
+ // we assign to it in the constructor of this class. Here, we
+ // use the lower 6 bits to select this toplevel actor type.
+ // This approach has the advantage that the tests will always
+ // select the same toplevel actor type deterministically,
+ // independent of the order they appeared and independent
+ // of the type of fuzzing we are doing.
+ auto portInstances = portNames[portIndex & 0x3f];
+ if (!portInstances.size()) {
+ return false;
+ }
+
+ if (useLastActor) {
+ useLastActor--;
+ *name = lastActorPortName;
+
+ MOZ_FUZZING_NYX_PRINT("DEBUG: MakeTargetDecision: Pinned to last actor.\n");
+
+ // Once we stop pinning to the last actor, we need to decide if we
+ // want to keep the pinning on the port itself. We use one of the
+ // unused upper bits of portIndex for this purpose.
+ if (!useLastActor && (portIndex & (1 << 7))) {
+ if (mIPCTriggerMsg == ipcDefaultTriggerMsg) {
+ MOZ_FUZZING_NYX_PRINT(
+ "DEBUG: MakeTargetDecision: Released pinning on last port.\n");
+ useLastPortName = false;
+ }
+ }
+ } else if (useLastPortName) {
+ *name = lastActorPortName;
+ MOZ_FUZZING_NYX_PRINT("DEBUG: MakeTargetDecision: Pinned to last port.\n");
+ } else {
+ *name = portInstances[portInstanceIndex % portInstances.size()];
+ }
+
+ // We should always have at least one actor per port
+ auto result = actorIds.find(*name);
+ if (result == actorIds.end()) {
+ MOZ_FUZZING_NYX_PRINT("ERROR: Couldn't find port in actors map?!\n");
+ return false;
+ }
+
+ // Find a random actor on this port
+ auto actors = result->second;
+ if (actors.empty()) {
+ MOZ_FUZZING_NYX_PRINT(
+ "ERROR: Couldn't find an actor for selected port?!\n");
+ return false;
+ }
+
+ auto seqNos = portSeqNos[*name];
+
+ // Hand out the correct sequence numbers
+ *seqno = seqNos.first - 1;
+ *fseqno = seqNos.second + 1;
+
+ // If a type is already specified, we must be in preserveHeaderMode.
+ bool isPreserveHeader = *type;
+
+ if (useLastActor) {
+ actorIndex = actors.size() - 1;
+ } else if (isPreserveHeader) {
+ // In preserveHeaderMode, we need to find an actor that matches the
+ // requested message type instead of any random actor.
+ uint16_t maybeProtocolId = *type >> 16;
+ if (maybeProtocolId >= IPCMessageStart::LastMsgIndex) {
+ // Not a valid protocol.
+ return false;
+ }
+ ProtocolId wantedProtocolId = static_cast<ProtocolId>(maybeProtocolId);
+ std::vector<uint32_t> allowedIndices;
+ for (uint32_t i = 0; i < actors.size(); ++i) {
+ if (actors[i].second == wantedProtocolId) {
+ allowedIndices.push_back(i);
+ }
+ }
+
+ if (allowedIndices.empty()) {
+ return false;
+ }
+
+ actorIndex = allowedIndices[actorIndex % allowedIndices.size()];
+ } else {
+ actorIndex %= actors.size();
+ }
+
+ ActorIdPair ids = actors[actorIndex];
+ *actorId = ids.first;
+
+ // If the actor ID is 0, then we are talking to the toplevel actor
+ // of this port. Hence we must set the ID to MSG_ROUTING_CONTROL.
+ if (!*actorId) {
+ *actorId = MSG_ROUTING_CONTROL;
+ }
+
+ if (!isPreserveHeader) {
+ // If msgType is already set, then we are in preserveHeaderMode
+ if (!this->GetRandomIPCMessageType(ids.second, typeOffset, type)) {
+ MOZ_FUZZING_NYX_PRINT("ERROR: GetRandomIPCMessageType failed?!\n");
+ return false;
+ }
+
+ *is_cons = false;
+ if (constructorTypes.find(*type) != constructorTypes.end()) {
+ *is_cons = true;
+ }
+ }
+
+ MOZ_FUZZING_NYX_PRINTF(
+ "DEBUG: MakeTargetDecision: Top-Level Protocol: %s Protocol: %s msgType: "
+ "%s (%u), Actor Instance %u of %zu, actor ID: %d, PreservedHeader: %d\n",
+ portNameToProtocolName[*name].c_str(), ProtocolIdToName(ids.second),
+ IPC::StringFromIPCMessageType(*type), *type, actorIndex, actors.size(),
+ *actorId, isPreserveHeader);
+
+ if (update) {
+ portSeqNos.insert_or_assign(*name,
+ std::pair<int32_t, uint64_t>(*seqno, *fseqno));
+ }
+
+ return true;
+}
+
+void IPCFuzzController::OnMessageTaskStart() { messageStartCount++; }
+
+void IPCFuzzController::OnMessageTaskStop() { messageStopCount++; }
+
+void IPCFuzzController::OnPreFuzzMessageTaskRun() { messageTaskCount++; }
+void IPCFuzzController::OnPreFuzzMessageTaskStop() { messageTaskCount--; }
+
+void IPCFuzzController::OnDropPeer(const char* reason = nullptr,
+ const char* file = nullptr, int line = 0) {
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (!Nyx::instance().started()) {
+ // It's possible to close a connection to some peer before we have even
+ // started fuzzing. We ignore these events until we are actually fuzzing.
+ return;
+ }
+
+ MOZ_FUZZING_NYX_PRINT(
+ "ERROR: ======== END OF ITERATION (DROP_PEER) ========\n");
+#ifdef FUZZ_DEBUG
+ MOZ_FUZZING_NYX_PRINTF("DEBUG: ======== %s:%d ========\n", file, line);
+#endif
+ Nyx::instance().handle_event("MOZ_IPC_DROP_PEER", file, line, reason);
+
+ if (Nyx::instance().is_replay()) {
+ // In replay mode, let's ignore drop peer to avoid races with it.
+ return;
+ }
+
+ Nyx::instance().release(IPCFuzzController::instance().getMessageStopCount());
+}
+
+void IPCFuzzController::StartFuzzing(mozilla::ipc::NodeChannel* channel,
+ IPC::Message& aMessage) {
+ nodeChannel = channel;
+
+ RefPtr<IPCFuzzLoop> runnable = new IPCFuzzLoop();
+
+#if MOZ_FUZZ_IPC_SYNC_INJECT
+ runnable->Run();
+#else
+ nsCOMPtr<nsIThread> newThread;
+ nsresult rv =
+ NS_NewNamedThread("IPCFuzzLoop", getter_AddRefs(newThread), runnable);
+
+ if (NS_FAILED(rv)) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: [StartFuzzing] NS_NewNamedThread failed?!\n");
+ }
+#endif
+}
+
+IPCFuzzController::IPCFuzzLoop::IPCFuzzLoop()
+ : mozilla::Runnable("IPCFuzzLoop") {}
+
+NS_IMETHODIMP IPCFuzzController::IPCFuzzLoop::Run() {
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: BEGIN IPCFuzzLoop::Run()\n");
+
+ const size_t maxMsgSize = 2048;
+ const size_t controlLen = 16;
+
+ Vector<char, 256, InfallibleAllocPolicy> buffer;
+
+ RefPtr<NodeController> controller = NodeController::GetSingleton();
+
+ // TODO: The following code is full of data races. We need synchronization
+ // on the `IPCFuzzController` instance, because the I/O thread can call into
+ // this class via ObserveIPCMessages. The problem is that any such call
+ // must either be observed to update the sequence numbers, or the packet
+ // must be dropped already.
+ if (!IPCFuzzController::instance().haveTargetNodeName) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: I don't have the target NodeName?!\n");
+ }
+
+ {
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: IPCFuzzLoop::Run() Mutex try\n");
+ // Called on the I/O thread and modifies `portSeqNos` and `actorIds`.
+ MutexAutoLock lock(IPCFuzzController::instance().mMutex);
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: IPCFuzzLoop::Run() Mutex locked\n");
+
+ // The wait/delay logic in ObserveIPCMessage should ensure that we haven't
+ // seen any packets on ports for which we haven't received actor information
+ // yet, if those ports belong to our channel. However, we might also have
+ // seen ports not belonging to our channel, which we have to remove now.
+ for (auto iter = IPCFuzzController::instance().portSeqNos.begin();
+ iter != IPCFuzzController::instance().portSeqNos.end();) {
+ auto result = IPCFuzzController::instance().actorIds.find(iter->first);
+ if (result == IPCFuzzController::instance().actorIds.end()) {
+ auto portNameResult =
+ IPCFuzzController::instance().portNodeName.find(iter->first);
+ if (portNameResult->second ==
+ IPCFuzzController::instance().targetNodeName) {
+ MOZ_FUZZING_NYX_PRINT(
+ "ERROR: We should not have port map entries without a "
+ "corresponding "
+ "entry in our actors map\n");
+ MOZ_REALLY_CRASH(__LINE__);
+ } else {
+ iter = IPCFuzzController::instance().portSeqNos.erase(iter);
+ }
+ } else {
+ ++iter;
+ }
+ }
+
+ // TODO: Technically, at this point we only know that PContent (or whatever
+ // toplevel protocol we decided to synchronize on), is present. It might
+ // be possible that others aren't created yet and we are racing on this.
+ //
+ // Note: The delay logic mentioned above makes this less likely. Only actors
+ // which are created on-demand and which have not been referenced yet at all
+ // would be affected by such a race.
+ for (auto iter = IPCFuzzController::instance().actorIds.begin();
+ iter != IPCFuzzController::instance().actorIds.end(); ++iter) {
+ bool isValidTarget = false;
+ Maybe<PortStatus> status;
+ PortRef ref = controller->GetPort(iter->first);
+ if (ref.is_valid()) {
+ status = controller->GetStatus(ref);
+ if (status) {
+ isValidTarget = status->peer_node_name ==
+ IPCFuzzController::instance().targetNodeName;
+ }
+ }
+
+ auto result = IPCFuzzController::instance().portSeqNos.find(iter->first);
+ if (result == IPCFuzzController::instance().portSeqNos.end()) {
+ if (isValidTarget) {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: Using Port %lu %lu for protocol %s (*)\n", iter->first.v1,
+ iter->first.v2, ProtocolIdToName(iter->second[0].second));
+
+ // Normally the start sequence numbers would be -1 and 1, but our map
+ // does not record the next numbers, but the "last seen" state. So we
+ // have to adjust these so the next calculated sequence number pair
+ // matches the start sequence numbers.
+ IPCFuzzController::instance().portSeqNos.insert_or_assign(
+ iter->first, std::pair<int32_t, uint64_t>(0, 0));
+
+ IPCFuzzController::instance().AddToplevelActor(
+ iter->first, iter->second[0].second);
+
+ } else {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: Removing Port %lu %lu for protocol %s (*)\n",
+ iter->first.v1, iter->first.v2,
+ ProtocolIdToName(iter->second[0].second));
+
+ // This toplevel actor does not belong to us, but we haven't added
+ // it to `portSeqNos`, so we don't have to remove it.
+ }
+ } else {
+ if (isValidTarget) {
+ MOZ_FUZZING_NYX_PRINTF("INFO: Using Port %lu %lu for protocol %s\n",
+ iter->first.v1, iter->first.v2,
+ ProtocolIdToName(iter->second[0].second));
+
+ IPCFuzzController::instance().AddToplevelActor(
+ iter->first, iter->second[0].second);
+ } else {
+ MOZ_FUZZING_NYX_PRINTF(
+ "INFO: Removing Port %lu %lu for protocol %s\n", iter->first.v1,
+ iter->first.v2, ProtocolIdToName(iter->second[0].second));
+
+ // This toplevel actor does not belong to us, so remove it.
+ IPCFuzzController::instance().portSeqNos.erase(result);
+ }
+ }
+ }
+ }
+
+ IPCFuzzController::instance().runnableDone = false;
+
+ SyncRunnable::DispatchToThread(
+ GetMainThreadSerialEventTarget(),
+ NS_NewRunnableFunction("IPCFuzzController::StartFuzzing", [&]() -> void {
+ MOZ_FUZZING_NYX_PRINT("INFO: Main thread runnable start.\n");
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+ MOZ_FUZZING_NYX_PRINT("INFO: Main thread runnable done.\n");
+ }));
+
+ MOZ_FUZZING_NYX_PRINT("INFO: Performing snapshot...\n");
+ Nyx::instance().start();
+
+ uint32_t expected_messages = 0;
+
+ if (!buffer.initLengthUninitialized(maxMsgSize)) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: Failed to initialize buffer!\n");
+ }
+
+ for (int i = 0; i < 3; ++i) {
+ // Grab enough data to potentially fill our everything except the footer.
+ uint32_t bufsize =
+ Nyx::instance().get_data((uint8_t*)buffer.begin(), buffer.length());
+
+ if (bufsize == 0xFFFFFFFF) {
+ // Done constructing
+ MOZ_FUZZING_NYX_DEBUG("Iteration complete: Out of data.\n");
+ break;
+ }
+
+ // Payload must be int aligned
+ bufsize -= bufsize % 4;
+
+ // Need at least a header and the control bytes.
+ if (bufsize < sizeof(IPC::Message::Header) + controlLen) {
+ MOZ_FUZZING_NYX_DEBUG("INFO: Not enough data to craft IPC message.\n");
+ continue;
+ }
+
+ const uint8_t* controlData = (uint8_t*)buffer.begin();
+
+ char* ipcMsgData = buffer.begin() + controlLen;
+ size_t ipcMsgLen = bufsize - controlLen;
+
+ bool preserveHeader = controlData[15] == 0xFF;
+
+ if (!preserveHeader) {
+ // Copy the header of the original message
+ memcpy(ipcMsgData, IPCFuzzController::instance().sampleHeader.begin(),
+ sizeof(IPC::Message::Header));
+ }
+
+ IPC::Message::Header* ipchdr = (IPC::Message::Header*)ipcMsgData;
+
+ ipchdr->payload_size = ipcMsgLen - sizeof(IPC::Message::Header);
+
+ PortName new_port_name;
+ int32_t new_seqno;
+ uint64_t new_fseqno;
+
+ int32_t actorId;
+ uint32_t msgType = 0;
+ bool isConstructor = false;
+ // Control Data Layout (16 byte)
+ // Byte 0 - Port Index (selects out of the valid ports seen)
+ // Byte 1 - Actor Index (selects one of the actors for that port)
+ // Byte 2 - Type Offset (select valid type for the specified actor)
+ // Byte 3 - ^- continued
+ // Byte 4 - Sync Bit
+ // Byte 5 - Optionally select a particular instance of the selected
+ // port type. Some toplevel protocols can have multiple
+ // instances running at the same time.
+ //
+ // Byte 15 - If set to 0xFF, skip overwriting the header, leave fields
+ // like message type intact and only set target actor and
+ // other fields that are dynamic.
+
+ uint8_t portIndex = controlData[0];
+ uint8_t actorIndex = controlData[1];
+ uint16_t typeOffset = *(uint16_t*)(&controlData[2]);
+ bool isSync = controlData[4] > 127;
+ uint8_t portInstanceIndex = controlData[5];
+
+ UniquePtr<IPC::Message> msg(new IPC::Message(ipcMsgData, ipcMsgLen));
+
+ if (preserveHeader) {
+ isConstructor = msg->is_constructor();
+ isSync = msg->is_sync();
+ msgType = msg->header()->type;
+
+ if (!msgType) {
+ // msgType == 0 is used to indicate to MakeTargetDecision that we are
+ // not in preserve header mode. It's not a valid message type in any
+ // case and we can error out early.
+ Nyx::instance().release(
+ IPCFuzzController::instance().getMessageStopCount());
+ }
+ }
+
+ if (!IPCFuzzController::instance().MakeTargetDecision(
+ portIndex, portInstanceIndex, actorIndex, typeOffset,
+ &new_port_name, &new_seqno, &new_fseqno, &actorId, &msgType,
+ &isConstructor)) {
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: MakeTargetDecision returned false.\n");
+ continue;
+ }
+
+ if (Nyx::instance().is_replay()) {
+ MOZ_FUZZING_NYX_PRINT("INFO: Replaying IPC packet with payload:\n");
+ for (uint32_t i = 0; i < ipcMsgLen - sizeof(IPC::Message::Header); ++i) {
+ if (i % 16 == 0) {
+ MOZ_FUZZING_NYX_PRINT("\n ");
+ }
+
+ MOZ_FUZZING_NYX_PRINTF(
+ "0x%02X ",
+ (unsigned char)(ipcMsgData[sizeof(IPC::Message::Header) + i]));
+ }
+ MOZ_FUZZING_NYX_PRINT("\n");
+ }
+
+ if (isConstructor) {
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Sending constructor message...\n");
+ msg->header()->flags.SetConstructor();
+ }
+
+ if (!isConstructor && isSync) {
+ MOZ_FUZZING_NYX_DEBUG("INFO: Sending sync message...\n");
+ msg->header()->flags.SetSync();
+ }
+
+ msg->set_seqno(new_seqno);
+ msg->set_routing_id(actorId);
+
+ if (!preserveHeader) {
+ // TODO: There is no setter for this.
+ msg->header()->type = msgType;
+ }
+
+ // Create the footer
+ auto messageEvent = MakeUnique<UserMessageEvent>(0);
+ messageEvent->set_port_name(new_port_name);
+ messageEvent->set_sequence_num(new_fseqno);
+
+ Vector<char, 256, InfallibleAllocPolicy> footerBuffer;
+ (void)footerBuffer.initLengthUninitialized(
+ messageEvent->GetSerializedSize());
+ messageEvent->Serialize(footerBuffer.begin());
+
+ msg->WriteFooter(footerBuffer.begin(), footerBuffer.length());
+ msg->set_event_footer_size(footerBuffer.length());
+
+ // This marks the message as a fuzzing message. Without this, it will
+ // be ignored by MessageTask and also not even scheduled by NodeChannel
+ // in asynchronous mode. We use this to ignore any IPC activity that
+ // happens just while we are fuzzing.
+ msg->SetFuzzMsg();
+
+#ifdef FUZZ_DEBUG
+ MOZ_FUZZING_NYX_PRINTF(
+ "DEBUG: OnEventMessage iteration %d, EVS: %u Payload: %u.\n", i,
+ ipchdr->event_footer_size, ipchdr->payload_size);
+#endif
+
+#ifdef FUZZ_DEBUG
+ MOZ_FUZZING_NYX_PRINTF("DEBUG: OnEventMessage: Port %lu %lu. Actor %d\n",
+ new_port_name.v1, new_port_name.v2, actorId);
+ MOZ_FUZZING_NYX_PRINTF(
+ "DEBUG: OnEventMessage: Flags: %u TxID: %d Handles: %u\n",
+ msg->header()->flags, msg->header()->txid, msg->header()->num_handles);
+#endif
+
+ // The number of messages we expect to see stopped.
+ expected_messages++;
+
+#if MOZ_FUZZ_IPC_SYNC_INJECT
+ // For synchronous injection, we just call OnMessageReceived directly.
+ IPCFuzzController::instance().nodeChannel->OnMessageReceived(
+ std::move(msg));
+#else
+ // For asynchronous injection, we have to post to the I/O thread instead.
+ XRE_GetIOMessageLoop()->PostTask(NS_NewRunnableFunction(
+ "NodeChannel::OnMessageReceived",
+ [msg = std::move(msg),
+ nodeChannel =
+ RefPtr{IPCFuzzController::instance().nodeChannel}]() mutable {
+ int32_t msgType = msg->header()->type;
+
+ // By default, we sync on the target thread of the receiving actor.
+ bool syncOnIOThread = false;
+
+ switch (msgType) {
+ case DATA_PIPE_CLOSED_MESSAGE_TYPE:
+ case DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE:
+ case ACCEPT_INVITE_MESSAGE_TYPE:
+ case REQUEST_INTRODUCTION_MESSAGE_TYPE:
+ case INTRODUCE_MESSAGE_TYPE:
+ case BROADCAST_MESSAGE_TYPE:
+ // This set of special messages will not be routed to actors and
+ // therefore we won't see these as stopped messages later. These
+ // messages are either used by NodeChannel, DataPipe or
+ // MessageChannel without creating MessageTasks. As such, the best
+ // we can do is synchronize on this thread. We do this by
+ // emulating the MessageTaskStart/Stop behavior that normal event
+ // messages have.
+ syncOnIOThread = true;
+ break;
+ default:
+ // Synchronization will happen in MessageChannel. Note that this
+ // also applies to certain special message types, as long as they
+ // are received by actors and not intercepted earlier.
+ break;
+ }
+
+ if (syncOnIOThread) {
+ mozilla::fuzzing::IPCFuzzController::instance()
+ .OnMessageTaskStart();
+ }
+
+ nodeChannel->OnMessageReceived(std::move(msg));
+
+ if (syncOnIOThread) {
+ mozilla::fuzzing::IPCFuzzController::instance().OnMessageTaskStop();
+
+ // Don't continue for now after sending such a special message.
+ // It can cause ports to go away and further messages can time out.
+ Nyx::instance().release(
+ IPCFuzzController::instance().getMessageStopCount());
+ }
+ }));
+#endif
+
+#ifdef MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Synchronizing after message...\n");
+ IPCFuzzController::instance().SynchronizeOnMessageExecution(
+ expected_messages);
+
+ SyncRunnable::DispatchToThread(
+ GetMainThreadSerialEventTarget(),
+ NS_NewRunnableFunction(
+ "IPCFuzzController::StartFuzzing", [&]() -> void {
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable start.\n");
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable done.\n");
+ }));
+#else
+
+ if (isConstructor) {
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: Synchronizing due to constructor message...\n");
+ IPCFuzzController::instance().SynchronizeOnMessageExecution(
+ expected_messages);
+ }
+#endif
+ }
+
+#ifndef MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Synchronizing due to end of iteration...\n");
+ IPCFuzzController::instance().SynchronizeOnMessageExecution(
+ expected_messages);
+
+ SyncRunnable::DispatchToThread(
+ GetMainThreadSerialEventTarget(),
+ NS_NewRunnableFunction("IPCFuzzController::StartFuzzing", [&]() -> void {
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable start.\n");
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable done.\n");
+ }));
+#endif
+
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: ======== END OF ITERATION (RELEASE) ========\n");
+
+ Nyx::instance().release(IPCFuzzController::instance().getMessageStopCount());
+
+ // Never reached.
+ return NS_OK;
+}
+
+void IPCFuzzController::SynchronizeOnMessageExecution(
+ uint32_t expected_messages) {
+ // This synchronization will work in both the sync and async case.
+ // For the async case, it is important to wait for the exact stop count
+ // because the message task is not even started potentially when we
+ // read this loop.
+ int hang_timeout = 10 * 1000;
+ while (IPCFuzzController::instance().getMessageStopCount() !=
+ expected_messages) {
+#ifdef FUZZ_DEBUG
+ uint32_t count_stopped =
+ IPCFuzzController::instance().getMessageStopCount();
+ uint32_t count_live = IPCFuzzController::instance().getMessageStartCount();
+ MOZ_FUZZING_NYX_PRINTF(
+ "DEBUG: Post Constructor: %d stopped messages (%d live, %d "
+ "expected)!\n",
+ count_stopped, count_live, expected_messages);
+#endif
+ PR_Sleep(PR_MillisecondsToInterval(50));
+ hang_timeout -= 50;
+
+ if (hang_timeout <= 0) {
+ Nyx::instance().handle_event("MOZ_TIMEOUT", nullptr, 0, nullptr);
+ MOZ_FUZZING_NYX_PRINT(
+ "ERROR: ======== END OF ITERATION (TIMEOUT) ========\n");
+ Nyx::instance().release(
+ IPCFuzzController::instance().getMessageStopCount());
+ }
+ }
+}
+
+static void dumpIPCMessageToFile(const UniquePtr<IPC::Message>& aMsg,
+ uint32_t aDumpCount, bool aUseNyx = false) {
+ if (Nyx::instance().is_replay()) {
+ return;
+ }
+
+ std::stringstream dumpFilename;
+ std::string msgName(IPC::StringFromIPCMessageType(aMsg->type()));
+ std::replace(msgName.begin(), msgName.end(), ':', '_');
+
+ if (aUseNyx) {
+ dumpFilename << "seeds/";
+ }
+
+ dumpFilename << msgName << aDumpCount << ".bin";
+
+ Pickle::BufferList::IterImpl iter(aMsg->Buffers());
+ Vector<char, 256, InfallibleAllocPolicy> dumpBuffer;
+ if (!dumpBuffer.initLengthUninitialized(sizeof(IPC::Message::Header) +
+ aMsg->Buffers().Size())) {
+ MOZ_FUZZING_NYX_ABORT("dumpBuffer.initLengthUninitialized failed\n");
+ }
+ if (!aMsg->Buffers().ReadBytes(
+ iter,
+ reinterpret_cast<char*>(dumpBuffer.begin() +
+ sizeof(IPC::Message::Header)),
+ dumpBuffer.length() - sizeof(IPC::Message::Header))) {
+ MOZ_FUZZING_NYX_ABORT("ReadBytes failed\n");
+ }
+ memcpy(dumpBuffer.begin(), aMsg->header(), sizeof(IPC::Message::Header));
+
+ if (aUseNyx) {
+ MOZ_FUZZING_NYX_PRINTF("INFO: Calling dump_file: %s Size: %zu\n",
+ dumpFilename.str().c_str(), dumpBuffer.length());
+ Nyx::instance().dump_file(reinterpret_cast<char*>(dumpBuffer.begin()),
+ dumpBuffer.length(), dumpFilename.str().c_str());
+ } else {
+ std::fstream file;
+ file.open(dumpFilename.str(), std::ios::out | std::ios::binary);
+ file.write(reinterpret_cast<char*>(dumpBuffer.begin()),
+ dumpBuffer.length());
+ file.close();
+ }
+}
+
+UniquePtr<IPC::Message> IPCFuzzController::replaceIPCMessage(
+ UniquePtr<IPC::Message> aMsg) {
+ if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_SingleMessage")) {
+ // Fuzzer is not enabled.
+ return aMsg;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // For now we only care about things in the parent process.
+ return aMsg;
+ }
+
+ static bool dumpFilterInitialized = false;
+ static std::string dumpFilter;
+ if (!dumpFilterInitialized) {
+ const char* dumpFilterStr = getenv("MOZ_FUZZ_DUMP_FILTER");
+ if (dumpFilterStr) {
+ dumpFilter = std::string(dumpFilterStr);
+ }
+ dumpFilterInitialized = true;
+ }
+
+ if (aMsg->type() != mIPCTriggerMsg) {
+ if ((mIPCDumpMsg && aMsg->type() == mIPCDumpMsg.value()) ||
+ (mIPCDumpAllMsgsSize.isSome() &&
+ aMsg->Buffers().Size() >= mIPCDumpAllMsgsSize.value())) {
+ if (!dumpFilter.empty()) {
+ std::string msgName(IPC::StringFromIPCMessageType(aMsg->type()));
+ if (msgName.find(dumpFilter) != std::string::npos) {
+ dumpIPCMessageToFile(aMsg, mIPCDumpCount);
+ mIPCDumpCount++;
+ }
+ } else {
+ dumpIPCMessageToFile(aMsg, mIPCDumpCount);
+ mIPCDumpCount++;
+ }
+ }
+
+ // Not the trigger message. Output additional information here for
+ // automation purposes. This shouldn't be an issue as we will only
+ // output these messages until we take a snapshot.
+ MOZ_FUZZING_NYX_PRINTF("INFO: [OnIPCMessage] Message: %s Size: %u\n",
+ IPC::StringFromIPCMessageType(aMsg->type()),
+ aMsg->header()->payload_size);
+ return aMsg;
+ } else {
+ // Dump the trigger message through Nyx in case we want to use it
+ // as a seed to AFL++ outside of the VM.
+ dumpIPCMessageToFile(aMsg, mIPCDumpCount, true /* aUseNyx */);
+ mIPCDumpCount++;
+ if (mIPCTriggerSingleMsgWait > 0) {
+ mIPCTriggerSingleMsgWait--;
+ return aMsg;
+ }
+ }
+
+ const size_t maxMsgSize = 4096;
+
+ Vector<char, 256, InfallibleAllocPolicy> buffer;
+ if (!buffer.initLengthUninitialized(maxMsgSize)) {
+ MOZ_FUZZING_NYX_ABORT("ERROR: Failed to initialize buffer!\n");
+ }
+
+ char* ipcMsgData = buffer.begin();
+
+ // //
+ // *** Snapshot Point *** //
+ // //
+ MOZ_FUZZING_NYX_PRINT("INFO: Performing snapshot...\n");
+ Nyx::instance().start();
+
+ IPCFuzzController::instance().useLastActor = 0;
+ IPCFuzzController::instance().useLastPortName = false;
+
+ MOZ_FUZZING_NYX_DEBUG("DEBUG: Requesting data...\n");
+
+ // Grab enough data to send at most `maxMsgSize` bytes
+ uint32_t bufsize =
+ Nyx::instance().get_raw_data((uint8_t*)buffer.begin(), buffer.length());
+
+ if (bufsize == 0xFFFFFFFF) {
+ MOZ_FUZZING_NYX_DEBUG("Nyx: Out of data.\n");
+ Nyx::instance().release(0);
+ }
+
+#ifdef FUZZ_DEBUG
+ MOZ_FUZZING_NYX_PRINTF("DEBUG: Got buffer of size %u...\n", bufsize);
+#endif
+
+ // Payload must be int aligned
+ bufsize -= bufsize % 4;
+
+ // Need at least a header and the control bytes.
+ if (bufsize < sizeof(IPC::Message::Header)) {
+ MOZ_FUZZING_NYX_DEBUG("INFO: Not enough data to craft IPC message.\n");
+ Nyx::instance().release(0);
+ }
+
+ buffer.shrinkTo(bufsize);
+
+ // Copy the header of the original message
+ memcpy(ipcMsgData, aMsg->header(), sizeof(IPC::Message::Header));
+ IPC::Message::Header* ipchdr = (IPC::Message::Header*)ipcMsgData;
+
+ size_t ipcMsgLen = buffer.length();
+ ipchdr->payload_size = ipcMsgLen - sizeof(IPC::Message::Header);
+
+ if (Nyx::instance().is_replay()) {
+ MOZ_FUZZING_NYX_PRINT("INFO: Replaying IPC packet with payload:\n");
+ for (uint32_t i = 0; i < ipcMsgLen - sizeof(IPC::Message::Header); ++i) {
+ if (i % 16 == 0) {
+ MOZ_FUZZING_NYX_PRINT("\n ");
+ }
+
+ MOZ_FUZZING_NYX_PRINTF(
+ "0x%02X ",
+ (unsigned char)(ipcMsgData[sizeof(IPC::Message::Header) + i]));
+ }
+ MOZ_FUZZING_NYX_PRINT("\n");
+ }
+
+ UniquePtr<IPC::Message> msg(new IPC::Message(ipcMsgData, ipcMsgLen));
+
+ // This marks the message as a fuzzing message. Without this, it will
+ // be ignored by MessageTask and also not even scheduled by NodeChannel
+ // in asynchronous mode. We use this to ignore any IPC activity that
+ // happens just while we are fuzzing.
+ msg->SetFuzzMsg();
+
+ return msg;
+}
+
+void IPCFuzzController::syncAfterReplace() {
+ if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_SingleMessage")) {
+ // Fuzzer is not enabled.
+ return;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // For now we only care about things in the parent process.
+ return;
+ }
+
+ if (!Nyx::instance().started()) {
+ // Not started yet
+ return;
+ }
+
+ MOZ_FUZZING_NYX_DEBUG(
+ "DEBUG: ======== END OF ITERATION (RELEASE) ========\n");
+
+ Nyx::instance().release(1);
+}
+
+} // namespace fuzzing
+} // namespace mozilla
diff --git a/tools/fuzzing/ipc/IPCFuzzController.h b/tools/fuzzing/ipc/IPCFuzzController.h
new file mode 100644
index 0000000000..756a68f38f
--- /dev/null
+++ b/tools/fuzzing/ipc/IPCFuzzController.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_ipc_IPCFuzzController_h
+#define mozilla_ipc_IPCFuzzController_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/fuzzing/Nyx.h"
+#include "mozilla/ipc/MessageLink.h"
+
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+
+#include "chrome/common/ipc_message.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/event.h"
+
+#include "IPCMessageStart.h"
+
+#include <unordered_map>
+#include <unordered_set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#define MOZ_FUZZING_IPC_DROP_PEER(aReason) \
+ mozilla::fuzzing::IPCFuzzController::instance().OnDropPeer( \
+ aReason, __FILE__, __LINE__);
+
+#define MOZ_FUZZING_IPC_MT_CTOR() \
+ mozilla::fuzzing::IPCFuzzController::instance().OnMessageTaskStart();
+
+#define MOZ_FUZZING_IPC_MT_STOP() \
+ mozilla::fuzzing::IPCFuzzController::instance().OnMessageTaskStop();
+
+#define MOZ_FUZZING_IPC_PRE_FUZZ_MT_RUN() \
+ mozilla::fuzzing::IPCFuzzController::instance().OnPreFuzzMessageTaskRun();
+
+#define MOZ_FUZZING_IPC_PRE_FUZZ_MT_STOP() \
+ mozilla::fuzzing::IPCFuzzController::instance().OnPreFuzzMessageTaskStop();
+
+namespace mozilla {
+
+namespace ipc {
+// We can't include ProtocolUtils.h here
+class IProtocol;
+typedef IPCMessageStart ProtocolId;
+
+class NodeChannel;
+} // namespace ipc
+
+namespace fuzzing {
+
+class IPCFuzzController {
+ typedef std::pair<int32_t, uint64_t> SeqNoPair;
+
+ typedef std::pair<int32_t, mozilla::ipc::ProtocolId> ActorIdPair;
+
+ class IPCFuzzLoop final : public Runnable {
+ friend class IPCFuzzController;
+
+ public:
+ NS_DECL_NSIRUNNABLE
+
+ IPCFuzzLoop();
+
+ private:
+ ~IPCFuzzLoop() = default;
+ };
+
+ public:
+ static IPCFuzzController& instance();
+
+ void InitializeIPCTypes();
+ bool GetRandomIPCMessageType(mozilla::ipc::ProtocolId pId,
+ uint16_t typeOffset, uint32_t* type);
+
+ bool ObserveIPCMessage(mozilla::ipc::NodeChannel* channel,
+ IPC::Message& aMessage);
+ bool MakeTargetDecision(uint8_t portIndex, uint8_t portInstanceIndex,
+ uint8_t actorIndex, uint16_t typeOffset,
+ mojo::core::ports::PortName* name, int32_t* seqno,
+ uint64_t* fseqno, int32_t* actorId, uint32_t* type,
+ bool* is_cons, bool update = true);
+
+ void OnActorConnected(mozilla::ipc::IProtocol* protocol);
+ void OnActorDestroyed(mozilla::ipc::IProtocol* protocol);
+ void OnMessageError(mozilla::ipc::HasResultCodes::Result code,
+ const IPC::Message& aMsg);
+ void OnDropPeer(const char* reason, const char* file, int line);
+ void OnMessageTaskStart();
+ void OnMessageTaskStop();
+ void OnPreFuzzMessageTaskRun();
+ void OnPreFuzzMessageTaskStop();
+ void OnChildReady() { childReady = true; }
+ void OnRunnableDone() { runnableDone = true; }
+
+ uint32_t getPreFuzzMessageTaskCount() { return messageTaskCount; };
+ uint32_t getMessageStartCount() { return messageStartCount; };
+ uint32_t getMessageStopCount() { return messageStopCount; };
+
+ void StartFuzzing(mozilla::ipc::NodeChannel* channel, IPC::Message& aMessage);
+
+ void SynchronizeOnMessageExecution(uint32_t expected_messages);
+ void AddToplevelActor(mojo::core::ports::PortName name,
+ mozilla::ipc::ProtocolId protocolId);
+
+ // Used for the IPC_SingleMessage fuzzer
+ UniquePtr<IPC::Message> replaceIPCMessage(UniquePtr<IPC::Message> aMsg);
+ void syncAfterReplace();
+
+ private:
+ // This is a mapping from port name to a pair of last seen sequence numbers.
+ std::unordered_map<mojo::core::ports::PortName, SeqNoPair> portSeqNos;
+
+ // This is a mapping from port name to node name.
+ std::unordered_map<mojo::core::ports::PortName, mojo::core::ports::NodeName>
+ portNodeName;
+
+ // This is a mapping from port name to protocol name, purely for debugging.
+ std::unordered_map<mojo::core::ports::PortName, std::string>
+ portNameToProtocolName;
+
+ // This maps each ProtocolId (IPCMessageStart) to the number of valid
+ // messages for that particular type.
+ std::unordered_map<mozilla::ipc::ProtocolId, uint32_t> validMsgTypes;
+
+ // This is a mapping from port name to pairs of actor Id and ProtocolId.
+ std::unordered_map<mojo::core::ports::PortName, std::vector<ActorIdPair>>
+ actorIds;
+
+ // If set, `lastActorPortName` is valid and fuzzing is pinned to this port.
+ Atomic<bool> useLastPortName;
+
+ // Last port where a new actor appeared. Only valid with `useLastPortName`.
+ mojo::core::ports::PortName lastActorPortName;
+
+ // Counter to indicate how long fuzzing should stay pinned to the last
+ // actor that appeared on `lastActorPortName`.
+ Atomic<uint32_t> useLastActor;
+
+ // This is the deterministic ordering of toplevel actors for fuzzing.
+ // In this matrix, each row (toplevel index) corresponds to one toplevel
+ // actor *type* while each entry in that row is an instance of that type,
+ // since some actors usually have multiple instances alive while others
+ // don't. For the exact ordering, please check the constructor for this
+ // class.
+ std::vector<std::vector<mojo::core::ports::PortName>> portNames;
+ std::unordered_map<std::string, uint8_t> portNameToIndex;
+
+ // This is a set of all types that are constructors.
+ std::unordered_set<uint32_t> constructorTypes;
+
+ // This is the name of the target node. We select one Node based on a
+ // particular toplevel actor and then use this to pull in additional
+ // toplevel actors that are on the same node (i.e. belong to the same
+ // process pair).
+ mojo::core::ports::NodeName targetNodeName;
+ bool haveTargetNodeName = false;
+
+ // This indicates that we have started the fuzzing thread and fuzzing will
+ // begin shortly.
+ bool fuzzingStartPending = false;
+
+ // This is used as a signal from other threads that runnables we dispatched
+ // are completed. Right now we use this only when dispatching to the main
+ // thread to await the completion of all pending events.
+ Atomic<bool> runnableDone;
+
+ // This is used to signal that the other process we are talking to is ready
+ // to start fuzzing. In the case of Parent <-> Child, a special IPC message
+ // is used to signal this. We might not be able to start fuzzing immediately
+ // hough if not all toplevel actors have been created yet.
+ Atomic<bool> childReady;
+
+ // Current amount of pending message tasks.
+ Atomic<uint32_t> messageStartCount;
+ Atomic<uint32_t> messageStopCount;
+
+ Atomic<uint32_t> messageTaskCount;
+
+ Vector<char, 256, InfallibleAllocPolicy> sampleHeader;
+
+ mozilla::ipc::NodeChannel* nodeChannel = nullptr;
+
+ // This class is used both on the I/O and background threads as well as
+ // its own fuzzing thread. Those methods that alter non-threadsafe data
+ // structures need to aquire this mutex first.
+ Mutex mMutex; // MOZ_UNANNOTATED;
+
+ // Can be used to specify a non-standard trigger message, e.h. to target
+ // a specific actor.
+ uint32_t mIPCTriggerMsg;
+
+ // Used to dump IPC messages in single message mode
+ Maybe<uint32_t> mIPCDumpMsg;
+ Maybe<uint32_t> mIPCDumpAllMsgsSize;
+ uint32_t mIPCDumpCount = 0;
+
+ // Used to select a particular packet instance in single message mode
+ uint32_t mIPCTriggerSingleMsgWait = 0;
+
+ IPCFuzzController();
+ NYX_DISALLOW_COPY_AND_ASSIGN(IPCFuzzController);
+};
+
+} // namespace fuzzing
+} // namespace mozilla
+
+#endif
diff --git a/tools/fuzzing/ipc/moz.build b/tools/fuzzing/ipc/moz.build
new file mode 100644
index 0000000000..001c67ed37
--- /dev/null
+++ b/tools/fuzzing/ipc/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Library("fuzzer-ipc-protocol")
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/ipc",
+]
+
+SOURCES += [
+ "IPCFuzzController.cpp",
+]
+
+EXPORTS.mozilla.fuzzing += [
+ "IPCFuzzController.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")