1618 lines
57 KiB
C++
1618 lines
57 KiB
C++
/* -*- 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 <set>
|
|
#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),
|
|
useLastPortNameAlways(false),
|
|
protoFilterTargetExcludeToplevel(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;
|
|
portNameToIndex["PRemoteLazyInputStream"] = 9;
|
|
portNameToIndex["PRemoteWorkerService"] = 10;
|
|
portNameToIndex["PBackgroundLSDatabase"] = 11;
|
|
portNameToIndex["PRemoteWorkerNonLifeCycleOpController"] = 12;
|
|
portNameToIndex["PNotification"] = 13;
|
|
portNameToIndex["PRemoteWorkerDebuggerManager"] = 14;
|
|
portNameToIndex["PRemoteWorkerDebugger"] = 15;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Resolve potentially disallowed messages now that we have initialized IPC
|
|
// types.
|
|
InitDisallowedIPCTypes();
|
|
}
|
|
|
|
bool IPCFuzzController::GetRandomIPCMessageType(ProtocolId pId,
|
|
uint16_t typeOffset,
|
|
uint32_t* type) {
|
|
if (actorAllowedMessages.size() > 0) {
|
|
// We are fixed to a single actor with a particular message set allowed.
|
|
*type = actorAllowedMessages[typeOffset % actorAllowedMessages.size()];
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Check if we are allowed to send this message type.
|
|
if (actorDisallowedMessages.find(*type) != actorDisallowedMessages.end()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void IPCFuzzController::InitDisallowedIPCTypes() {
|
|
const char* targetMsgName = getenv("MOZ_FUZZ_IPC_MSGFILTER_DISALLOW");
|
|
if (!targetMsgName) {
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> targetMsgNames;
|
|
std::istringstream targetMsgNameStream(targetMsgName);
|
|
for (std::string msg; getline(targetMsgNameStream, msg, ';');) {
|
|
targetMsgNames.push_back(msg);
|
|
}
|
|
|
|
for (auto pIdEntry : validMsgTypes) {
|
|
for (uint16_t typeOffset = 0; typeOffset < pIdEntry.second; ++typeOffset) {
|
|
uint32_t type = ((uint32_t)pIdEntry.first << 16) + 1 + typeOffset;
|
|
const char* msgName = IPC::StringFromIPCMessageType(type);
|
|
if (strstr(msgName, "::Reply_")) {
|
|
continue;
|
|
}
|
|
|
|
for (std::string msg : targetMsgNames) {
|
|
if (strstr(msgName, msg.c_str())) {
|
|
actorDisallowedMessages.insert(type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IPCFuzzController::InitAllowedIPCTypes() {
|
|
const char* targetMsgName = getenv("MOZ_FUZZ_IPC_MSGFILTER_ALLOW");
|
|
if (!targetMsgName) {
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> targetMsgNames;
|
|
std::istringstream targetMsgNameStream(targetMsgName);
|
|
for (std::string msg; getline(targetMsgNameStream, msg, ';');) {
|
|
targetMsgNames.push_back(msg);
|
|
}
|
|
|
|
if (!maybeLastActorId) {
|
|
// We only want to call this if we are actually pinning to an actor.
|
|
// This also means that calling this is only valid with a PROTOID_FILTER
|
|
// set.
|
|
MOZ_FUZZING_NYX_ABORT("InitAllowedIPCTypes called without actor pinned?!");
|
|
}
|
|
|
|
auto result = actorIds.find(lastActorPortName);
|
|
if (result == actorIds.end()) {
|
|
MOZ_FUZZING_NYX_ABORT("ERROR: Couldn't find port in actors map?!\n");
|
|
}
|
|
auto actors = result->second;
|
|
|
|
bool found = false;
|
|
size_t actorIndex;
|
|
for (actorIndex = 0; actorIndex < actors.size(); ++actorIndex) {
|
|
if (actors[actorIndex].first == maybeLastActorId ||
|
|
(maybeLastActorId == MSG_ROUTING_CONTROL &&
|
|
!actors[actorIndex].first)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
MOZ_FUZZING_NYX_ABORT(
|
|
"ERROR: Pinned to actor that's not in actors map!?\n");
|
|
}
|
|
|
|
ActorIdPair ids = actors[actorIndex];
|
|
|
|
ProtocolId pId = ids.second;
|
|
|
|
auto pIdEntry = validMsgTypes.find(pId);
|
|
if (pIdEntry == validMsgTypes.end()) {
|
|
MOZ_FUZZING_NYX_ABORT("ERROR: Pinned actor has no valid message types!?\n");
|
|
}
|
|
|
|
for (uint16_t typeOffset = 0; typeOffset < pIdEntry->second; ++typeOffset) {
|
|
uint32_t type = ((uint32_t)pIdEntry->first << 16) + 1 + typeOffset;
|
|
const char* msgName = IPC::StringFromIPCMessageType(type);
|
|
if (strstr(msgName, "::Reply_")) {
|
|
continue;
|
|
}
|
|
|
|
for (std::string msg : targetMsgNames) {
|
|
if (strstr(msgName, msg.c_str())) {
|
|
actorAllowedMessages.push_back(type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!actorAllowedMessages.size()) {
|
|
MOZ_FUZZING_NYX_ABORT("ERROR: Empty actorAllowedMessages!?\n");
|
|
}
|
|
}
|
|
|
|
static bool IsManagedByTargetActor(IProtocol* protocol,
|
|
std::string& protoIdFilter) {
|
|
while (protocol) {
|
|
if (!strcmp(protocol->GetProtocolName(), protoIdFilter.c_str())) {
|
|
return true;
|
|
}
|
|
|
|
protocol = protocol->Manager();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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 allowSubActors =
|
|
!!getenv("MOZ_FUZZ_PROTOID_FILTER_ALLOW_SUBACTORS");
|
|
static std::string protoIdFilter;
|
|
if (!protoIdFilterInitialized) {
|
|
const char* protoIdFilterStr = getenv("MOZ_FUZZ_PROTOID_FILTER");
|
|
if (protoIdFilterStr) {
|
|
protoIdFilter = std::string(protoIdFilterStr);
|
|
}
|
|
protoIdFilterInitialized = true;
|
|
}
|
|
|
|
MessageChannel* channel = protocol->ToplevelProtocol()->GetIPCChannel();
|
|
|
|
Maybe<PortName> portName = channel->GetPortName();
|
|
|
|
if (!portName) {
|
|
MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %" PRId64
|
|
" Protocol: %s\n",
|
|
protocol->Id(), protocol->GetProtocolName());
|
|
}
|
|
|
|
if (portName) {
|
|
if (!protoIdFilter.empty()) {
|
|
if (!strcmp(protocol->GetProtocolName(), protoIdFilter.c_str())) {
|
|
MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %" PRId64
|
|
" Protocol: %s matches "
|
|
"target.\n",
|
|
protocol->Id(), protocol->GetProtocolName());
|
|
|
|
// If our matching protocol is not a toplevel actor, then we need to
|
|
// exclude the toplevel protocol later in `MakeTargetDecision` because
|
|
// the actor will always be added to the map.
|
|
protoFilterTargetExcludeToplevel = protocol->Manager() != nullptr;
|
|
} else if (actorIds[*portName].empty()) {
|
|
MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %" PRId64
|
|
" Protocol: %s is toplevel "
|
|
"actor.\n",
|
|
protocol->Id(), protocol->GetProtocolName());
|
|
} else if (allowSubActors &&
|
|
IsManagedByTargetActor(protocol, protoIdFilter)) {
|
|
MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %" PRId64
|
|
" Protocol: %s is managed by "
|
|
"target actor.\n",
|
|
protocol->Id(), protocol->GetProtocolName());
|
|
} else {
|
|
// Not a toplevel actor, not matching the filter and also either not a
|
|
// sub actor of our target or we are focusing only on the target. Ignore
|
|
// this actor.
|
|
if (!!getenv("MOZ_FUZZ_DEBUG")) {
|
|
MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %" PRId64
|
|
" Protocol: %s ignored due to "
|
|
"filter.\n",
|
|
protocol->Id(), protocol->GetProtocolName());
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!!getenv("MOZ_FUZZ_DEBUG")) {
|
|
MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %" PRId64
|
|
" Protocol: %s Port: %lu %lu\n",
|
|
protocol->Id(), protocol->GetProtocolName(),
|
|
portName->v1, portName->v2);
|
|
}
|
|
|
|
actorIds[*portName].emplace_back(protocol->Id(), protocol->GetProtocolId());
|
|
|
|
if (Nyx::instance().started()) {
|
|
if (!useLastPortNameAlways) {
|
|
// 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");
|
|
|
|
if (maybeLastActorId &&
|
|
(maybeLastActorId == protocol->Id() ||
|
|
(maybeLastActorId == MSG_ROUTING_CONTROL && !protocol->Id())) &&
|
|
lastActorPortName == *portName) {
|
|
MOZ_FUZZING_NYX_DEBUG("INFO: Actor pinning released.\n");
|
|
// We destroyed the actor we were focusing on, unpin.
|
|
maybeLastActorId = 0;
|
|
useLastActor = 0;
|
|
}
|
|
|
|
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 node %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_PRINTF("DEBUG: Ready message detected on actor %" PRId64
|
|
".\n",
|
|
aMessage.routing_id());
|
|
|
|
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();
|
|
useLastPortNameAlways = true;
|
|
|
|
if (!getenv("MOZ_FUZZ_PROTOID_FILTER_ALLOW_SUBACTORS")) {
|
|
// In this mode, we really want to focus on a single actor.
|
|
useLastActor = 1024;
|
|
maybeLastActorId = aMessage.routing_id();
|
|
MOZ_FUZZING_NYX_PRINTF("DEBUG: Pinned to actor %" PRId64 " forever.\n",
|
|
aMessage.routing_id());
|
|
} else {
|
|
// In this mode, we want to focus on a particular actor and all of its
|
|
// sub actors. This means we have to pin the port at least. Undesired
|
|
// other actors are filtered out already in OnActorConnected *except*
|
|
// for the toplevel actor belonging to this port. This exception is
|
|
// handled separately in MakeTargetDecision.
|
|
MOZ_FUZZING_NYX_PRINTF("DEBUG: Pinned to port %lu %lu forever.\n",
|
|
lastActorPortName.v1, lastActorPortName.v2);
|
|
}
|
|
|
|
InitAllowedIPCTypes();
|
|
}
|
|
|
|
// 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 node %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 node %lu %lu (fuzzing start pending)\n",
|
|
channel->GetName().v1, channel->GetName().v2);
|
|
channel->mBlockSendRecv = true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
MOZ_FUZZING_NYX_ABORT("Unreachable");
|
|
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<IPC::Message::seqno_t, uint64_t>(
|
|
aMessage.seqno(), userMsgEv->sequence_num()));
|
|
#ifdef FUZZ_DEBUG
|
|
MOZ_FUZZING_NYX_PRINTF(
|
|
"DEBUG: Port %lu %lu updated sequence number to %lu\n", name.v1,
|
|
name.v2, userMsgEv->sequence_num());
|
|
#endif
|
|
|
|
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::MsgDropped:
|
|
Nyx::instance().handle_event("MOZ_IPC_DROPPED", nullptr, 0, nullptr);
|
|
break;
|
|
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;
|
|
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,
|
|
uint8_t actorProtocolIndex, uint16_t typeOffset, PortName* name,
|
|
IPC::Message::seqno_t* seqno, uint64_t* fseqno,
|
|
mozilla::ipc::ActorId* actorId, uint32_t* type, bool* is_cons,
|
|
bool update) {
|
|
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 && !useLastPortNameAlways && (portIndex & (1 << 7))) {
|
|
MOZ_FUZZING_NYX_PRINT(
|
|
"DEBUG: MakeTargetDecision: Released pinning on last port.\n");
|
|
useLastPortName = false;
|
|
}
|
|
} else if (useLastPortName || useLastPortNameAlways) {
|
|
*name = lastActorPortName;
|
|
MOZ_FUZZING_NYX_PRINT("DEBUG: MakeTargetDecision: Pinned to last port.\n");
|
|
} else {
|
|
// 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;
|
|
}
|
|
*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) {
|
|
if (maybeLastActorId) {
|
|
bool found = false;
|
|
for (actorIndex = 0; actorIndex < actors.size(); ++actorIndex) {
|
|
// Toplevel actors have a discrepancy here: Routing ID is -1 but the
|
|
// actor id provided through protocol->Id() is 0.
|
|
if (actors[actorIndex].first == maybeLastActorId ||
|
|
(maybeLastActorId == MSG_ROUTING_CONTROL &&
|
|
!actors[actorIndex].first)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
MOZ_FUZZING_NYX_ABORT(
|
|
"ERROR: Pinned to actor that's not in actors map!?\n");
|
|
}
|
|
} else {
|
|
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 (protoFilterTargetExcludeToplevel && !i) {
|
|
// Filter out the toplevel protocol at index 0
|
|
continue;
|
|
}
|
|
|
|
if (actors[i].second == wantedProtocolId) {
|
|
allowedIndices.push_back(i);
|
|
}
|
|
}
|
|
|
|
if (allowedIndices.empty()) {
|
|
return false;
|
|
}
|
|
|
|
actorIndex = allowedIndices[actorIndex % allowedIndices.size()];
|
|
} else {
|
|
std::set<ProtocolId> seenProtocol;
|
|
std::vector<ProtocolId> availableProtocols;
|
|
|
|
if (protoFilterTargetExcludeToplevel && actors.size() < 2) {
|
|
// We likely destroyed all other actors
|
|
return false;
|
|
}
|
|
|
|
for (auto actor : actors) {
|
|
if (protoFilterTargetExcludeToplevel && seenProtocol.empty()) {
|
|
// Skip the toplevel protocol.
|
|
seenProtocol.insert(actor.second);
|
|
continue;
|
|
}
|
|
|
|
if (seenProtocol.find(actor.second) == seenProtocol.end()) {
|
|
seenProtocol.insert(actor.second);
|
|
availableProtocols.push_back(actor.second);
|
|
}
|
|
}
|
|
|
|
// Instead of directly selecting a random actor, we select the protocol
|
|
// first and then out of all available actors matching this protocol,
|
|
// we select the destination actor. This makes sure that we are uniformly
|
|
// fuzzing protocols and not biasing towards protocols with lots of actor
|
|
// instances.
|
|
ProtocolId wantedProtocolId =
|
|
availableProtocols[actorProtocolIndex % availableProtocols.size()];
|
|
|
|
std::vector<uint32_t> allowedIndices;
|
|
for (uint32_t i = 0; i < actors.size(); ++i) {
|
|
if (actors[i].second == wantedProtocolId) {
|
|
allowedIndices.push_back(i);
|
|
}
|
|
}
|
|
|
|
actorIndex = allowedIndices[actorIndex % allowedIndices.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 || actorAllowedMessages.size() > 0) {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
if (isPreserveHeader &&
|
|
actorDisallowedMessages.find(*type) != actorDisallowedMessages.end()) {
|
|
// If we have messages that aren't allowed to be sent, we need to
|
|
// confirm that the type set in the header is still allowed.
|
|
return false;
|
|
}
|
|
|
|
MOZ_FUZZING_NYX_PRINTF(
|
|
"DEBUG: MakeTargetDecision: Top-Level Protocol: %s Protocol: %s msgType: "
|
|
"%s (%u), Actor Instance %u of %zu, actor ID: %" PRId64
|
|
", 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<IPC::Message::seqno_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 &&
|
|
IPCFuzzController::instance().mIPCTriggerMsg ==
|
|
ipcDefaultTriggerMsg) {
|
|
MOZ_FUZZING_NYX_PRINTF(
|
|
"ERROR: We should not have port map entries without a "
|
|
"corresponding "
|
|
"entry in our actors map (Port %lu %lu)\n",
|
|
iter->first.v1, iter->first.v2);
|
|
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<IPC::Message::seqno_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;
|
|
IPC::Message::seqno_t new_seqno;
|
|
uint64_t new_fseqno;
|
|
|
|
mozilla::ipc::ActorId 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 - Actor Protocol Index (selects the protocol on that port)
|
|
// 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]);
|
|
uint8_t actorProtocolIndex = controlData[4];
|
|
uint8_t portInstanceIndex = controlData[5];
|
|
|
|
UniquePtr<IPC::Message> msg(new IPC::Message(ipcMsgData, ipcMsgLen));
|
|
|
|
if (preserveHeader) {
|
|
isConstructor = msg->is_constructor();
|
|
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, actorProtocolIndex,
|
|
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 (IPC::IPCMessageTypeIsSync(msgType)) {
|
|
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;
|
|
}
|
|
|
|
// Make sure we're not sending with LAZY_SEND
|
|
msg->header()->flags.mFlags &= ~IPC::Message::HeaderFlags::LAZY_SEND_BIT;
|
|
|
|
// 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
|
|
|
|
if (!!getenv("MOZ_FUZZ_DEBUG")) {
|
|
MOZ_FUZZING_NYX_PRINTF(
|
|
"INFO: Flags: IsSync: %d IsReply: %d IsReplyError: %d IsConstructor: "
|
|
"%d IsRelay: %d IsLazySend: %d\n",
|
|
msg->is_sync(), msg->is_reply(), msg->is_reply_error(),
|
|
msg->is_constructor(), msg->is_relay(), msg->is_lazy_send());
|
|
}
|
|
|
|
// 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_GetAsyncIOEventTarget()->Dispatch(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");
|
|
if (!!getenv("MOZ_FUZZ_CRASH_ON_TIMEOUT")) {
|
|
MOZ_DIAGNOSTIC_CRASH("IPCFuzzController Timeout");
|
|
}
|
|
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;
|
|
IPCFuzzController::instance().useLastPortNameAlways = 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 single 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));
|
|
|
|
if (!!getenv("MOZ_FUZZ_DEBUG")) {
|
|
MOZ_FUZZING_NYX_PRINTF("INFO: Name: %s Target: %" PRId64 "\n", msg->name(),
|
|
msg->routing_id());
|
|
}
|
|
|
|
// 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
|