/* -*- 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/. */
// 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
// 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;
// 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")));
}
}
for (uint32_t start = 0; start < LastMsgIndex; ++start) {
uint32_t i; for (i = (start << 16) + 1; i < ((start + 1) << 16); ++i) { constchar* 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()]; returntrue;
}
auto pIdEntry = validMsgTypes.find(pId); if (pIdEntry == validMsgTypes.end()) { returnfalse;
}
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; constchar* 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");
}
}
staticbool IsManagedByTargetActor(IProtocol* protocol,
std::string& protoIdFilter) { while (protocol) { if (!strcmp(protocol->GetProtocolName(), protoIdFilter.c_str())) { returntrue;
}
protocol = protocol->Manager();
} returnfalse;
}
void IPCFuzzController::OnActorConnected(IProtocol* protocol) { if (!XRE_IsParentProcess()) { return;
}
if (portName) { if (!protoIdFilter.empty()) { if (!strcmp(protocol->GetProtocolName(), protoIdFilter.c_str())) {
MOZ_FUZZING_NYX_PRINTF( "INFO: [OnActorConnected] ActorID %d 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;
} elseif (actorIds[*portName].empty()) {
MOZ_FUZZING_NYX_PRINTF( "INFO: [OnActorConnected] ActorID %d Protocol: %s is toplevel " "actor.\n",
protocol->Id(), protocol->GetProtocolName());
} elseif (allowSubActors &&
IsManagedByTargetActor(protocol, protoIdFilter)) {
MOZ_FUZZING_NYX_PRINTF( "INFO: [OnActorConnected] ActorID %d 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 %d Protocol: %s ignored due to " "filter.\n",
protocol->Id(), protocol->GetProtocolName());
} return;
}
}
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;
}
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");
}
}
bool IPCFuzzController::ObserveIPCMessage(mozilla::ipc::NodeChannel* channel,
IPC::Message& aMessage) { if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_Generic")) { // Fuzzer is not enabled. returntrue;
}
if (!XRE_IsParentProcess()) { // For now we only care about things in the parent process. returntrue;
}
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;
} returntrue;
} elseif (aMessage.type() == mIPCTriggerMsg && !Nyx::instance().started()) {
MOZ_FUZZING_NYX_PRINTF("DEBUG: Ready message detected on actor %d.\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");
}
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 %d 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));
}
} elseif (haveTargetNodeName && targetNodeName != channel->GetName()) { // Not our node, no need to observe returntrue;
} elseif (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;
} returnfalse;
}
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");
}
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);
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;
}
}
}
}
// 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;
}
// 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())); #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
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);
}
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;
}
} elseif (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()) { returnfalse;
}
*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"); returnfalse;
}
// 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"); returnfalse;
}
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;
}
} elseif (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. returnfalse;
}
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 (protoFilterTargetExcludeToplevel && actors.size() < 2) { // We likely destroyed all other actors returnfalse;
}
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);
}
}
// 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"); returnfalse;
}
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. returnfalse;
}
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));
}
void IPCFuzzController::OnDropPeer(constchar* reason = nullptr, constchar* 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;
}
// 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<int32_t, uint64_t>(0, 0));
} 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);
}
}
}
}
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;
}
if (!preserveHeader) { // Copy the header of the original message
memcpy(ipcMsgData, IPCFuzzController::instance().sampleHeader.begin(), sizeof(IPC::Message::Header));
}
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 - 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.
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 ");
}
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);
// 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();
// 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);
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");
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());
}
}
}
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");
}
// 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;
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 ");
}
// 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
¤ Dauer der Verarbeitung: 0.28 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.