fune/tools/fuzzing/ipc/IPCFuzzController.cpp

856 lines
30 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"
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
// For debugging purposes, it can be helpful to synchronize after each message
// rather than after each iteration, to see which messages are particularly
// slow or cause a hang. Without this, synchronization will occur at the end
// of each iteration as well as after each constructor message.
// #define MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG
namespace mozilla {
namespace fuzzing {
IPCFuzzController::IPCFuzzController() : mMutex("IPCFuzzController") {
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;
}
// static
IPCFuzzController& IPCFuzzController::instance() {
static IPCFuzzController ifc;
return ifc;
}
void IPCFuzzController::InitializeIPCTypes() {
const char* cons = "Constructor";
size_t cons_len = strlen(cons);
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;
size_t len = strlen(name);
if (len > cons_len && !memcmp(cons, name + len - cons_len, cons_len)) {
constructorTypes.insert(i);
}
}
validMsgTypes[(ProtocolId)start] = i - ((start << 16) + 1);
}
}
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);
return true;
}
void IPCFuzzController::OnActorConnected(IProtocol* protocol) {
if (!XRE_IsParentProcess()) {
return;
}
#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) {
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");
actorIds[*portName].emplace_back(protocol->Id(), protocol->GetProtocolId());
// 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_PRINT("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);
}
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() == dom::PContent::Msg_SignalFuzzingReady__ID) {
MOZ_FUZZING_NYX_PRINT("DEBUG: Ready message detected.\n");
// 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) {
MOZ_FUZZING_NYX_PRINT("ERROR: Received ready signal twice?!\n");
MOZ_REALLY_CRASH(__LINE__);
}
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.
{
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;
}
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))) {
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 (update) {
portSeqNos.insert_or_assign(*name,
std::pair<int32_t, uint64_t>(*seqno, *fseqno));
}
if (useLastActor) {
actorIndex = actors.size() - 1;
} 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 (!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;
}
#ifdef FUZZ_DEBUG
MOZ_FUZZING_NYX_PRINTF(
"DEBUG: MakeTargetDecision: Protocol: %s msgType: %s\n",
ProtocolIdToName(ids.second), IPC::StringFromIPCMessageType(*type));
#endif
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;
IPCFuzzController::instance().useLastActor = 0;
IPCFuzzController::instance().useLastPortName = false;
if (!buffer.initLengthUninitialized(maxMsgSize)) {
MOZ_FUZZING_NYX_ABORT("ERROR: Failed to initialize buffer!\n");
}
for (int i = 0; i < 16; ++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;
// 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;
bool isConstructor;
// 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.
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];
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");
}
UniquePtr<IPC::Message> msg(new IPC::Message(ipcMsgData, ipcMsgLen));
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);
// 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 {
nodeChannel->OnMessageReceived(std::move(msg));
}));
#endif
#ifdef MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG
MOZ_FUZZING_NYX_DEBUG("DEBUG: Synchronizing after message...\n");
IPCFuzzController::instance().SynchronizeOnMessageExecution(
expected_messages);
#else
if (isConstructor) {
MOZ_FUZZING_NYX_DEBUG(
"DEBUG: Synchronizing due to constructor message...\n");
IPCFuzzController::instance().SynchronizeOnMessageExecution(
expected_messages);
}
#endif
}
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");
}));
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());
}
}
}
} // namespace fuzzing
} // namespace mozilla