/* -*- 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 #include #include using namespace mojo::core::ports; using namespace mozilla::ipc; // Sync inject means that the actual fuzzing takes place on the I/O thread // and hence it injects directly into the target NodeChannel. In async mode, // we run the fuzzing on a separate thread and dispatch the runnable that // injects the message back to the I/O thread. Both approaches seem to work // and have advantages and disadvantages. Blocking the I/O thread means no // IPC between other processes will interfere with our fuzzing in the meantime // but blocking could also cause hangs when such IPC is required during the // fuzzing runtime for some reason. // #define MOZ_FUZZ_IPC_SYNC_INJECT 1 // Synchronize after each message rather than just after every constructor // or at the end of the iteration. Doing so costs us some performance because // we have to wait for each packet and process events on the main thread, // but it is necessary when using `OnMessageError` to release on early errors. #define MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG 1 namespace mozilla { namespace fuzzing { const uint32_t ipcDefaultTriggerMsg = dom::PContent::Msg_SignalFuzzingReady__ID; IPCFuzzController::IPCFuzzController() : useLastPortName(false), useLastActor(0), mMutex("IPCFuzzController"), mIPCTriggerMsg(ipcDefaultTriggerMsg) { InitializeIPCTypes(); // We use 6 bits for port index selection without wrapping, so we just // create 64 empty rows in our port matrix. Not all of these rows will // be used though. portNames.resize(64); // This is our port / toplevel actor ordering. Add new toplevel actors // here to support them in the fuzzer. Do *NOT* change the order of // these, as it will invalidate our fuzzing corpus. portNameToIndex["PContent"] = 0; portNameToIndex["PBackground"] = 1; portNameToIndex["PBackgroundStarter"] = 2; portNameToIndex["PCompositorManager"] = 3; portNameToIndex["PImageBridge"] = 4; portNameToIndex["PProcessHangMonitor"] = 5; portNameToIndex["PProfiler"] = 6; portNameToIndex["PVRManager"] = 7; portNameToIndex["PCanvasManager"] = 8; // Used to select the n-th trigger message as a starting point for fuzzing // in single message mode. A value of 1 will skip the first matching message // and start fuzzing on the second message, and so on. if (!!getenv("MOZ_FUZZ_IPC_TRIGGER_SINGLEMSG_WAIT")) { mIPCTriggerSingleMsgWait = atoi(getenv("MOZ_FUZZ_IPC_TRIGGER_SINGLEMSG_WAIT")); } // When set, dump all IPC message at or above the specified size to files. // Useful to collect samples of different types in one run. if (!!getenv("MOZ_FUZZ_IPC_DUMP_ALL_MSGS_SIZE")) { mIPCDumpAllMsgsSize.emplace( atoi(getenv("MOZ_FUZZ_IPC_DUMP_ALL_MSGS_SIZE"))); } } // static IPCFuzzController& IPCFuzzController::instance() { static IPCFuzzController ifc; return ifc; } void IPCFuzzController::InitializeIPCTypes() { const char* cons = "Constructor"; size_t cons_len = strlen(cons); const char* targetNameTrigger = getenv("MOZ_FUZZ_IPC_TRIGGER"); const char* targetNameDump = getenv("MOZ_FUZZ_IPC_DUMPMSG"); for (uint32_t start = 0; start < LastMsgIndex; ++start) { uint32_t i; for (i = (start << 16) + 1; i < ((start + 1) << 16); ++i) { const char* name = IPC::StringFromIPCMessageType(i); if (name[0] == '<') break; if (targetNameTrigger && !strcmp(name, targetNameTrigger)) { MOZ_FUZZING_NYX_PRINTF( "INFO: [InitializeIPCTypes] Located trigger message (%s, %d)\n", targetNameTrigger, i); mIPCTriggerMsg = i; } if (targetNameDump && !strcmp(name, targetNameDump)) { MOZ_FUZZING_NYX_PRINTF( "INFO: [InitializeIPCTypes] Located dump message (%s, %d)\n", targetNameDump, i); mIPCDumpMsg.emplace(i); } size_t len = strlen(name); if (len > cons_len && !memcmp(cons, name + len - cons_len, cons_len)) { constructorTypes.insert(i); } } uint32_t msgCount = i - ((start << 16) + 1); if (msgCount) { validMsgTypes[(ProtocolId)start] = msgCount; } } } bool IPCFuzzController::GetRandomIPCMessageType(ProtocolId pId, uint16_t typeOffset, uint32_t* type) { auto pIdEntry = validMsgTypes.find(pId); if (pIdEntry == validMsgTypes.end()) { return false; } *type = ((uint32_t)pIdEntry->first << 16) + 1 + (typeOffset % pIdEntry->second); if (strstr(IPC::StringFromIPCMessageType(*type), "::Reply_")) { *type = *type - 1; } return true; } void IPCFuzzController::OnActorConnected(IProtocol* protocol) { if (!XRE_IsParentProcess()) { return; } MOZ_FUZZING_NYX_DEBUG( "DEBUG: IPCFuzzController::OnActorConnected() Mutex try\n"); // Called on background threads and modifies `actorIds`. MutexAutoLock lock(mMutex); MOZ_FUZZING_NYX_DEBUG( "DEBUG: IPCFuzzController::OnActorConnected() Mutex locked\n"); static bool protoIdFilterInitialized = false; static bool allowNewActors = false; static std::string protoIdFilter; if (!protoIdFilterInitialized) { const char* protoIdFilterStr = getenv("MOZ_FUZZ_PROTOID_FILTER"); if (protoIdFilterStr) { protoIdFilter = std::string(protoIdFilterStr); } protoIdFilterInitialized = true; } #ifdef FUZZ_DEBUG MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorConnected] ActorID %d Protocol: %s\n", protocol->Id(), protocol->GetProtocolName()); #endif MessageChannel* channel = protocol->ToplevelProtocol()->GetIPCChannel(); Maybe portName = channel->GetPortName(); if (portName) { if (!protoIdFilter.empty() && (!Nyx::instance().started() || !allowNewActors) && strcmp(protocol->GetProtocolName(), protoIdFilter.c_str()) && !actorIds[*portName].empty()) { MOZ_FUZZING_NYX_PRINTF( "INFO: [OnActorConnected] ActorID %d Protocol: %s ignored due to " "filter.\n", protocol->Id(), protocol->GetProtocolName()); return; } else if (!protoIdFilter.empty() && !strcmp(protocol->GetProtocolName(), protoIdFilter.c_str())) { MOZ_FUZZING_NYX_PRINTF( "INFO: [OnActorConnected] ActorID %d Protocol: %s matches target.\n", protocol->Id(), protocol->GetProtocolName()); } else if (!protoIdFilter.empty() && actorIds[*portName].empty()) { MOZ_FUZZING_NYX_PRINTF( "INFO: [OnActorConnected] ActorID %d Protocol: %s is toplevel " "actor.\n", protocol->Id(), protocol->GetProtocolName()); } actorIds[*portName].emplace_back(protocol->Id(), protocol->GetProtocolId()); if (Nyx::instance().started() && protoIdFilter.empty()) { // Fix the port we will be using for at least the next 5 messages useLastPortName = true; lastActorPortName = *portName; // Use this actor for the next 5 messages useLastActor = 5; } } else { MOZ_FUZZING_NYX_DEBUG("WARNING: No port name on actor?!\n"); } } void IPCFuzzController::OnActorDestroyed(IProtocol* protocol) { if (!XRE_IsParentProcess()) { return; } #ifdef FUZZ_DEBUG MOZ_FUZZING_NYX_PRINTF("INFO: [OnActorDestroyed] ActorID %d Protocol: %s\n", protocol->Id(), protocol->GetProtocolName()); #endif MessageChannel* channel = protocol->ToplevelProtocol()->GetIPCChannel(); Maybe portName = channel->GetPortName(); if (portName) { MOZ_FUZZING_NYX_DEBUG( "DEBUG: IPCFuzzController::OnActorDestroyed() Mutex try\n"); // Called on background threads and modifies `actorIds`. MutexAutoLock lock(mMutex); MOZ_FUZZING_NYX_DEBUG( "DEBUG: IPCFuzzController::OnActorDestroyed() Mutex locked\n"); for (auto iter = actorIds[*portName].begin(); iter != actorIds[*portName].end();) { if (iter->first == protocol->Id() && iter->second == protocol->GetProtocolId()) { iter = actorIds[*portName].erase(iter); } else { ++iter; } } } else { MOZ_FUZZING_NYX_DEBUG("WARNING: No port name on destroyed actor?!\n"); } } void IPCFuzzController::AddToplevelActor(PortName name, ProtocolId protocolId) { const char* protocolName = ProtocolIdToName(protocolId); auto result = portNameToIndex.find(protocolName); if (result == portNameToIndex.end()) { MOZ_FUZZING_NYX_PRINTF( "ERROR: [OnActorConnected] Unknown Top-Level Protocol: %s\n", protocolName); MOZ_FUZZING_NYX_ABORT("Unknown Top-Level Protocol\n"); } uint8_t portIndex = result->second; portNames[portIndex].push_back(name); portNameToProtocolName[name] = std::string(protocolName); } bool IPCFuzzController::ObserveIPCMessage(mozilla::ipc::NodeChannel* channel, IPC::Message& aMessage) { if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_Generic")) { // Fuzzer is not enabled. return true; } if (!XRE_IsParentProcess()) { // For now we only care about things in the parent process. return true; } if (aMessage.IsFuzzMsg()) { // Don't observe our own messages. If this is the first fuzzing message, // we also block further non-fuzzing communication on that node. if (!channel->mBlockSendRecv) { MOZ_FUZZING_NYX_PRINTF( "INFO: [NodeChannel::OnMessageReceived] Blocking further " "communication on Port %lu %lu (seen fuzz msg)\n", channel->GetName().v1, channel->GetName().v2); channel->mBlockSendRecv = true; } return true; } else if (aMessage.type() == mIPCTriggerMsg && !Nyx::instance().started()) { MOZ_FUZZING_NYX_PRINT("DEBUG: Ready message detected.\n"); if (!haveTargetNodeName && !!getenv("MOZ_FUZZ_PROTOID_FILTER")) { // With a protocol filter set, we want to pin to the actor that // received the ready message and stay there. We should do this here // because OnActorConnected can be called even after the ready message // has been received and potentially override the correct actor. // Get the port name associated with this message Vector 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::Deserialize(footer.begin(), footer.length()); if (!event || event->type() != Event::kUserMessage) { MOZ_FUZZING_NYX_ABORT("ERROR: Trigger message is not kUserMessage?!\n"); } lastActorPortName = event->port_name(); useLastPortName = true; useLastActor = 1024; } // TODO: This is specific to PContent fuzzing. If we later want to fuzz // a different process pair, we need additional signals here. OnChildReady(); // The ready message indicates the right node name for us to work with // and we should only ever receive it once. if (!haveTargetNodeName) { targetNodeName = channel->GetName(); haveTargetNodeName = true; // We can also use this message as the base template for other messages if (!this->sampleHeader.initLengthUninitialized( sizeof(IPC::Message::Header))) { MOZ_FUZZING_NYX_ABORT("sampleHeader.initLengthUninitialized failed\n"); } memcpy(sampleHeader.begin(), aMessage.header(), sizeof(IPC::Message::Header)); } } else if (haveTargetNodeName && targetNodeName != channel->GetName()) { // Not our node, no need to observe return true; } else if (Nyx::instance().started()) { // When fuzzing is already started, we shouldn't observe messages anymore. if (!channel->mBlockSendRecv) { MOZ_FUZZING_NYX_PRINTF( "INFO: [NodeChannel::OnMessageReceived] Blocking further " "communication on Port %lu %lu (fuzzing started)\n", channel->GetName().v1, channel->GetName().v2); channel->mBlockSendRecv = true; } return false; } Vector 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::Deserialize(footer.begin(), footer.length()); if (!event) { MOZ_FUZZING_NYX_ABORT("ERROR: Failed to deserialize observed message?!\n"); } if (event->type() == Event::kUserMessage) { if (haveTargetNodeName && !fuzzingStartPending) { bool missingActor = false; // Check if we have any entries in our port map that we haven't seen yet // though `OnActorConnected`. That method is called on a background // thread and this call will race with the I/O thread. // // However, with a custom MOZ_FUZZ_IPC_TRIGGER we assume we want to keep // the port pinned so we don't have to wait at all. if (mIPCTriggerMsg == ipcDefaultTriggerMsg) { MOZ_FUZZING_NYX_DEBUG( "DEBUG: IPCFuzzController::ObserveIPCMessage() Mutex try\n"); // Called on the I/O thread and reads `portSeqNos`. // // IMPORTANT: We must give up any locks before entering `StartFuzzing`, // as we will never return. This would cause a deadlock with new actors // being created and `OnActorConnected` being called. MutexAutoLock lock(mMutex); MOZ_FUZZING_NYX_DEBUG( "DEBUG: IPCFuzzController::ObserveIPCMessage() Mutex locked\n"); for (auto iter = portSeqNos.begin(); iter != portSeqNos.end(); ++iter) { auto result = actorIds.find(iter->first); if (result == actorIds.end()) { // Make sure we only wait for actors that belong to us. auto result = portNodeName.find(iter->first); if (result->second == targetNodeName) { missingActor = true; break; } } } } if (missingActor) { MOZ_FUZZING_NYX_PRINT( "INFO: Delaying fuzzing start, missing actors...\n"); } else if (!childReady) { MOZ_FUZZING_NYX_PRINT( "INFO: Delaying fuzzing start, waiting for child...\n"); } else { fuzzingStartPending = true; StartFuzzing(channel, aMessage); // In the async case, we return and can already block the relevant // communication. if (targetNodeName == channel->GetName()) { if (!channel->mBlockSendRecv) { MOZ_FUZZING_NYX_PRINTF( "INFO: [NodeChannel::OnMessageReceived] Blocking further " "communication on Port %lu %lu (fuzzing start pending)\n", channel->GetName().v1, channel->GetName().v2); channel->mBlockSendRecv = true; } return false; } return true; } } // Add/update sequence numbers. We need to make sure to do this after our // call to `StartFuzzing` because once we start fuzzing, the message will // never actually be processed, so we run into a sequence number desync. { // Get the port name associated with this message UserMessageEvent* userMsgEv = static_cast(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(aMessage.seqno(), userMsgEv->sequence_num())); portNodeName.insert_or_assign(name, channel->GetName()); } } return true; } void IPCFuzzController::OnMessageError( mozilla::ipc::HasResultCodes::Result code, const IPC::Message& aMsg) { if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_Generic")) { // Fuzzer is not enabled. return; } if (!XRE_IsParentProcess()) { // For now we only care about things in the parent process. return; } if (!aMsg.IsFuzzMsg()) { // We should only act upon fuzzing messages. return; } switch (code) { case ipc::HasResultCodes::MsgNotKnown: // Seeing this error should be rare - one potential reason is if a sync // message is sent as async and vice versa. Other than that, we shouldn't // be generating this error at all. Nyx::instance().handle_event("MOZ_IPC_UNKNOWN_TYPE", nullptr, 0, nullptr); #ifdef FUZZ_DEBUG MOZ_FUZZING_NYX_PRINTF( "WARNING: MOZ_IPC_UNKNOWN_TYPE for message type %s (%u) routed to " "actor %d (sync %d)\n", IPC::StringFromIPCMessageType(aMsg.type()), aMsg.type(), aMsg.routing_id(), aMsg.is_sync()); #endif break; case ipc::HasResultCodes::MsgNotAllowed: Nyx::instance().handle_event("MOZ_IPC_NOTALLOWED_ERROR", nullptr, 0, nullptr); break; case ipc::HasResultCodes::MsgPayloadError: case ipc::HasResultCodes::MsgValueError: Nyx::instance().handle_event("MOZ_IPC_DESERIALIZE_ERROR", nullptr, 0, nullptr); break; case ipc::HasResultCodes::MsgProcessingError: Nyx::instance().handle_event("MOZ_IPC_PROCESS_ERROR", nullptr, 0, nullptr); break; case ipc::HasResultCodes::MsgRouteError: Nyx::instance().handle_event("MOZ_IPC_ROUTE_ERROR", nullptr, 0, nullptr); break; default: MOZ_FUZZING_NYX_ABORT("unknown Result code"); } // Count this message as one iteration as well. Nyx::instance().release(IPCFuzzController::instance().getMessageStopCount() + 1); } bool IPCFuzzController::MakeTargetDecision( uint8_t portIndex, uint8_t portInstanceIndex, uint8_t actorIndex, uint16_t typeOffset, PortName* name, int32_t* seqno, uint64_t* fseqno, int32_t* actorId, uint32_t* type, bool* is_cons, bool update) { // Every possible toplevel actor type has a fixed number that // we assign to it in the constructor of this class. Here, we // use the lower 6 bits to select this toplevel actor type. // This approach has the advantage that the tests will always // select the same toplevel actor type deterministically, // independent of the order they appeared and independent // of the type of fuzzing we are doing. auto portInstances = portNames[portIndex & 0x3f]; if (!portInstances.size()) { return false; } if (useLastActor) { useLastActor--; *name = lastActorPortName; MOZ_FUZZING_NYX_PRINT("DEBUG: MakeTargetDecision: Pinned to last actor.\n"); // Once we stop pinning to the last actor, we need to decide if we // want to keep the pinning on the port itself. We use one of the // unused upper bits of portIndex for this purpose. if (!useLastActor && (portIndex & (1 << 7))) { if (mIPCTriggerMsg == ipcDefaultTriggerMsg) { MOZ_FUZZING_NYX_PRINT( "DEBUG: MakeTargetDecision: Released pinning on last port.\n"); useLastPortName = false; } } } else if (useLastPortName) { *name = lastActorPortName; MOZ_FUZZING_NYX_PRINT("DEBUG: MakeTargetDecision: Pinned to last port.\n"); } else { *name = portInstances[portInstanceIndex % portInstances.size()]; } // We should always have at least one actor per port auto result = actorIds.find(*name); if (result == actorIds.end()) { MOZ_FUZZING_NYX_PRINT("ERROR: Couldn't find port in actors map?!\n"); return false; } // Find a random actor on this port auto actors = result->second; if (actors.empty()) { MOZ_FUZZING_NYX_PRINT( "ERROR: Couldn't find an actor for selected port?!\n"); return false; } auto seqNos = portSeqNos[*name]; // Hand out the correct sequence numbers *seqno = seqNos.first - 1; *fseqno = seqNos.second + 1; // If a type is already specified, we must be in preserveHeaderMode. bool isPreserveHeader = *type; if (useLastActor) { actorIndex = actors.size() - 1; } else if (isPreserveHeader) { // In preserveHeaderMode, we need to find an actor that matches the // requested message type instead of any random actor. uint16_t maybeProtocolId = *type >> 16; if (maybeProtocolId >= IPCMessageStart::LastMsgIndex) { // Not a valid protocol. return false; } ProtocolId wantedProtocolId = static_cast(maybeProtocolId); std::vector allowedIndices; for (uint32_t i = 0; i < actors.size(); ++i) { if (actors[i].second == wantedProtocolId) { allowedIndices.push_back(i); } } if (allowedIndices.empty()) { return false; } actorIndex = allowedIndices[actorIndex % allowedIndices.size()]; } else { actorIndex %= actors.size(); } ActorIdPair ids = actors[actorIndex]; *actorId = ids.first; // If the actor ID is 0, then we are talking to the toplevel actor // of this port. Hence we must set the ID to MSG_ROUTING_CONTROL. if (!*actorId) { *actorId = MSG_ROUTING_CONTROL; } if (!isPreserveHeader) { // If msgType is already set, then we are in preserveHeaderMode if (!this->GetRandomIPCMessageType(ids.second, typeOffset, type)) { MOZ_FUZZING_NYX_PRINT("ERROR: GetRandomIPCMessageType failed?!\n"); return false; } *is_cons = false; if (constructorTypes.find(*type) != constructorTypes.end()) { *is_cons = true; } } MOZ_FUZZING_NYX_PRINTF( "DEBUG: MakeTargetDecision: Top-Level Protocol: %s Protocol: %s msgType: " "%s (%u), Actor Instance %u of %zu, actor ID: %d, PreservedHeader: %d\n", portNameToProtocolName[*name].c_str(), ProtocolIdToName(ids.second), IPC::StringFromIPCMessageType(*type), *type, actorIndex, actors.size(), *actorId, isPreserveHeader); if (update) { portSeqNos.insert_or_assign(*name, std::pair(*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 runnable = new IPCFuzzLoop(); #if MOZ_FUZZ_IPC_SYNC_INJECT runnable->Run(); #else nsCOMPtr 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 buffer; RefPtr 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 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(0, 0)); IPCFuzzController::instance().AddToplevelActor( iter->first, iter->second[0].second); } else { MOZ_FUZZING_NYX_PRINTF( "INFO: Removing Port %lu %lu for protocol %s (*)\n", iter->first.v1, iter->first.v2, ProtocolIdToName(iter->second[0].second)); // This toplevel actor does not belong to us, but we haven't added // it to `portSeqNos`, so we don't have to remove it. } } else { if (isValidTarget) { MOZ_FUZZING_NYX_PRINTF("INFO: Using Port %lu %lu for protocol %s\n", iter->first.v1, iter->first.v2, ProtocolIdToName(iter->second[0].second)); IPCFuzzController::instance().AddToplevelActor( iter->first, iter->second[0].second); } else { MOZ_FUZZING_NYX_PRINTF( "INFO: Removing Port %lu %lu for protocol %s\n", iter->first.v1, iter->first.v2, ProtocolIdToName(iter->second[0].second)); // This toplevel actor does not belong to us, so remove it. IPCFuzzController::instance().portSeqNos.erase(result); } } } } IPCFuzzController::instance().runnableDone = false; SyncRunnable::DispatchToThread( GetMainThreadSerialEventTarget(), NS_NewRunnableFunction("IPCFuzzController::StartFuzzing", [&]() -> void { MOZ_FUZZING_NYX_PRINT("INFO: Main thread runnable start.\n"); NS_ProcessPendingEvents(NS_GetCurrentThread()); MOZ_FUZZING_NYX_PRINT("INFO: Main thread runnable done.\n"); })); MOZ_FUZZING_NYX_PRINT("INFO: Performing snapshot...\n"); Nyx::instance().start(); uint32_t expected_messages = 0; if (!buffer.initLengthUninitialized(maxMsgSize)) { MOZ_FUZZING_NYX_ABORT("ERROR: Failed to initialize buffer!\n"); } for (int i = 0; i < 3; ++i) { // Grab enough data to potentially fill our everything except the footer. uint32_t bufsize = Nyx::instance().get_data((uint8_t*)buffer.begin(), buffer.length()); if (bufsize == 0xFFFFFFFF) { // Done constructing MOZ_FUZZING_NYX_DEBUG("Iteration complete: Out of data.\n"); break; } // Payload must be int aligned bufsize -= bufsize % 4; // Need at least a header and the control bytes. if (bufsize < sizeof(IPC::Message::Header) + controlLen) { MOZ_FUZZING_NYX_DEBUG("INFO: Not enough data to craft IPC message.\n"); continue; } const uint8_t* controlData = (uint8_t*)buffer.begin(); char* ipcMsgData = buffer.begin() + controlLen; size_t ipcMsgLen = bufsize - controlLen; bool preserveHeader = controlData[15] == 0xFF; if (!preserveHeader) { // Copy the header of the original message memcpy(ipcMsgData, IPCFuzzController::instance().sampleHeader.begin(), sizeof(IPC::Message::Header)); } IPC::Message::Header* ipchdr = (IPC::Message::Header*)ipcMsgData; ipchdr->payload_size = ipcMsgLen - sizeof(IPC::Message::Header); PortName new_port_name; int32_t new_seqno; uint64_t new_fseqno; int32_t actorId; uint32_t msgType = 0; bool isConstructor = false; // Control Data Layout (16 byte) // Byte 0 - Port Index (selects out of the valid ports seen) // Byte 1 - Actor Index (selects one of the actors for that port) // Byte 2 - Type Offset (select valid type for the specified actor) // Byte 3 - ^- continued // Byte 4 - Sync Bit // Byte 5 - Optionally select a particular instance of the selected // port type. Some toplevel protocols can have multiple // instances running at the same time. // // Byte 15 - If set to 0xFF, skip overwriting the header, leave fields // like message type intact and only set target actor and // other fields that are dynamic. uint8_t portIndex = controlData[0]; uint8_t actorIndex = controlData[1]; uint16_t typeOffset = *(uint16_t*)(&controlData[2]); uint8_t portInstanceIndex = controlData[5]; UniquePtr 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, 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; } // Create the footer auto messageEvent = MakeUnique(0); messageEvent->set_port_name(new_port_name); messageEvent->set_sequence_num(new_fseqno); Vector footerBuffer; (void)footerBuffer.initLengthUninitialized( messageEvent->GetSerializedSize()); messageEvent->Serialize(footerBuffer.begin()); msg->WriteFooter(footerBuffer.begin(), footerBuffer.length()); msg->set_event_footer_size(footerBuffer.length()); // This marks the message as a fuzzing message. Without this, it will // be ignored by MessageTask and also not even scheduled by NodeChannel // in asynchronous mode. We use this to ignore any IPC activity that // happens just while we are fuzzing. msg->SetFuzzMsg(); #ifdef FUZZ_DEBUG MOZ_FUZZING_NYX_PRINTF( "DEBUG: OnEventMessage iteration %d, EVS: %u Payload: %u.\n", i, ipchdr->event_footer_size, ipchdr->payload_size); #endif #ifdef FUZZ_DEBUG MOZ_FUZZING_NYX_PRINTF("DEBUG: OnEventMessage: Port %lu %lu. Actor %d\n", new_port_name.v1, new_port_name.v2, actorId); MOZ_FUZZING_NYX_PRINTF( "DEBUG: OnEventMessage: Flags: %u TxID: %d Handles: %u\n", msg->header()->flags, msg->header()->txid, msg->header()->num_handles); #endif // The number of messages we expect to see stopped. expected_messages++; #if MOZ_FUZZ_IPC_SYNC_INJECT // For synchronous injection, we just call OnMessageReceived directly. IPCFuzzController::instance().nodeChannel->OnMessageReceived( std::move(msg)); #else // For asynchronous injection, we have to post to the I/O thread instead. XRE_GetIOMessageLoop()->PostTask(NS_NewRunnableFunction( "NodeChannel::OnMessageReceived", [msg = std::move(msg), nodeChannel = RefPtr{IPCFuzzController::instance().nodeChannel}]() mutable { int32_t msgType = msg->header()->type; // By default, we sync on the target thread of the receiving actor. bool syncOnIOThread = false; switch (msgType) { case DATA_PIPE_CLOSED_MESSAGE_TYPE: case DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE: case ACCEPT_INVITE_MESSAGE_TYPE: case REQUEST_INTRODUCTION_MESSAGE_TYPE: case INTRODUCE_MESSAGE_TYPE: case BROADCAST_MESSAGE_TYPE: // This set of special messages will not be routed to actors and // therefore we won't see these as stopped messages later. These // messages are either used by NodeChannel, DataPipe or // MessageChannel without creating MessageTasks. As such, the best // we can do is synchronize on this thread. We do this by // emulating the MessageTaskStart/Stop behavior that normal event // messages have. syncOnIOThread = true; break; default: // Synchronization will happen in MessageChannel. Note that this // also applies to certain special message types, as long as they // are received by actors and not intercepted earlier. break; } if (syncOnIOThread) { mozilla::fuzzing::IPCFuzzController::instance() .OnMessageTaskStart(); } nodeChannel->OnMessageReceived(std::move(msg)); if (syncOnIOThread) { mozilla::fuzzing::IPCFuzzController::instance().OnMessageTaskStop(); // Don't continue for now after sending such a special message. // It can cause ports to go away and further messages can time out. Nyx::instance().release( IPCFuzzController::instance().getMessageStopCount()); } })); #endif #ifdef MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG MOZ_FUZZING_NYX_DEBUG("DEBUG: Synchronizing after message...\n"); IPCFuzzController::instance().SynchronizeOnMessageExecution( expected_messages); SyncRunnable::DispatchToThread( GetMainThreadSerialEventTarget(), NS_NewRunnableFunction( "IPCFuzzController::StartFuzzing", [&]() -> void { MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable start.\n"); NS_ProcessPendingEvents(NS_GetCurrentThread()); MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable done.\n"); })); #else if (isConstructor) { MOZ_FUZZING_NYX_DEBUG( "DEBUG: Synchronizing due to constructor message...\n"); IPCFuzzController::instance().SynchronizeOnMessageExecution( expected_messages); } #endif } #ifndef MOZ_FUZZ_IPC_SYNC_AFTER_EACH_MSG MOZ_FUZZING_NYX_DEBUG("DEBUG: Synchronizing due to end of iteration...\n"); IPCFuzzController::instance().SynchronizeOnMessageExecution( expected_messages); SyncRunnable::DispatchToThread( GetMainThreadSerialEventTarget(), NS_NewRunnableFunction("IPCFuzzController::StartFuzzing", [&]() -> void { MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable start.\n"); NS_ProcessPendingEvents(NS_GetCurrentThread()); MOZ_FUZZING_NYX_DEBUG("DEBUG: Main thread runnable done.\n"); })); #endif MOZ_FUZZING_NYX_DEBUG( "DEBUG: ======== END OF ITERATION (RELEASE) ========\n"); Nyx::instance().release(IPCFuzzController::instance().getMessageStopCount()); // Never reached. return NS_OK; } void IPCFuzzController::SynchronizeOnMessageExecution( uint32_t expected_messages) { // This synchronization will work in both the sync and async case. // For the async case, it is important to wait for the exact stop count // because the message task is not even started potentially when we // read this loop. int hang_timeout = 10 * 1000; while (IPCFuzzController::instance().getMessageStopCount() != expected_messages) { #ifdef FUZZ_DEBUG uint32_t count_stopped = IPCFuzzController::instance().getMessageStopCount(); uint32_t count_live = IPCFuzzController::instance().getMessageStartCount(); MOZ_FUZZING_NYX_PRINTF( "DEBUG: Post Constructor: %d stopped messages (%d live, %d " "expected)!\n", count_stopped, count_live, expected_messages); #endif PR_Sleep(PR_MillisecondsToInterval(50)); hang_timeout -= 50; if (hang_timeout <= 0) { Nyx::instance().handle_event("MOZ_TIMEOUT", nullptr, 0, nullptr); MOZ_FUZZING_NYX_PRINT( "ERROR: ======== END OF ITERATION (TIMEOUT) ========\n"); Nyx::instance().release( IPCFuzzController::instance().getMessageStopCount()); } } } static void dumpIPCMessageToFile(const UniquePtr& 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 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(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(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(dumpBuffer.begin()), dumpBuffer.length()); file.close(); } } UniquePtr IPCFuzzController::replaceIPCMessage( UniquePtr 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 buffer; if (!buffer.initLengthUninitialized(maxMsgSize)) { MOZ_FUZZING_NYX_ABORT("ERROR: Failed to initialize buffer!\n"); } char* ipcMsgData = buffer.begin(); // // // *** Snapshot Point *** // // // MOZ_FUZZING_NYX_PRINT("INFO: Performing snapshot...\n"); Nyx::instance().start(); IPCFuzzController::instance().useLastActor = 0; IPCFuzzController::instance().useLastPortName = false; MOZ_FUZZING_NYX_DEBUG("DEBUG: Requesting data...\n"); // Grab enough data to send at most `maxMsgSize` bytes uint32_t bufsize = Nyx::instance().get_raw_data((uint8_t*)buffer.begin(), buffer.length()); if (bufsize == 0xFFFFFFFF) { MOZ_FUZZING_NYX_DEBUG("Nyx: Out of data.\n"); Nyx::instance().release(0); } #ifdef FUZZ_DEBUG MOZ_FUZZING_NYX_PRINTF("DEBUG: Got buffer of size %u...\n", bufsize); #endif // Payload must be int aligned bufsize -= bufsize % 4; // Need at least a header and the control bytes. if (bufsize < sizeof(IPC::Message::Header)) { MOZ_FUZZING_NYX_DEBUG("INFO: Not enough data to craft IPC message.\n"); Nyx::instance().release(0); } buffer.shrinkTo(bufsize); // Copy the header of the original message memcpy(ipcMsgData, aMsg->header(), sizeof(IPC::Message::Header)); IPC::Message::Header* ipchdr = (IPC::Message::Header*)ipcMsgData; size_t ipcMsgLen = buffer.length(); ipchdr->payload_size = ipcMsgLen - sizeof(IPC::Message::Header); if (Nyx::instance().is_replay()) { MOZ_FUZZING_NYX_PRINT("INFO: Replaying IPC packet with payload:\n"); for (uint32_t i = 0; i < ipcMsgLen - sizeof(IPC::Message::Header); ++i) { if (i % 16 == 0) { MOZ_FUZZING_NYX_PRINT("\n "); } MOZ_FUZZING_NYX_PRINTF( "0x%02X ", (unsigned char)(ipcMsgData[sizeof(IPC::Message::Header) + i])); } MOZ_FUZZING_NYX_PRINT("\n"); } UniquePtr msg(new IPC::Message(ipcMsgData, ipcMsgLen)); // This marks the message as a fuzzing message. Without this, it will // be ignored by MessageTask and also not even scheduled by NodeChannel // in asynchronous mode. We use this to ignore any IPC activity that // happens just while we are fuzzing. msg->SetFuzzMsg(); return msg; } void IPCFuzzController::syncAfterReplace() { if (!mozilla::fuzzing::Nyx::instance().is_enabled("IPC_SingleMessage")) { // Fuzzer is not enabled. return; } if (!XRE_IsParentProcess()) { // For now we only care about things in the parent process. return; } if (!Nyx::instance().started()) { // Not started yet return; } MOZ_FUZZING_NYX_DEBUG( "DEBUG: ======== END OF ITERATION (RELEASE) ========\n"); Nyx::instance().release(1); } } // namespace fuzzing } // namespace mozilla