Skip to content

Commit

Permalink
Bug 1891945 - IPC Fuzzing: Improve the MOZ_FUZZ_IPC_PROTO code. r=truber
Browse files Browse the repository at this point in the history
  • Loading branch information
choller committed Apr 24, 2024
1 parent 2cc691a commit 17f7bf6
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 49 deletions.
183 changes: 135 additions & 48 deletions tools/fuzzing/ipc/IPCFuzzController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ const uint32_t ipcDefaultTriggerMsg = dom::PContent::Msg_SignalFuzzingReady__ID;

IPCFuzzController::IPCFuzzController()
: useLastPortName(false),
useLastPortNameAlways(false),
protoFilterTargetExcludeToplevel(false),
useLastActor(0),
mMutex("IPCFuzzController"),
mIPCTriggerMsg(ipcDefaultTriggerMsg) {
Expand Down Expand Up @@ -156,6 +158,18 @@ bool IPCFuzzController::GetRandomIPCMessageType(ProtocolId pId,
return true;
}

static bool IsManagedByTargetActor(IProtocol* protocol,
std::string& protoIdFilter) {
while (protocol) {
if (!strcmp(protocol->GetProtocolName(), protoIdFilter.c_str())) {
return true;
}

protocol = protocol->Manager();
}
return false;
}

void IPCFuzzController::OnActorConnected(IProtocol* protocol) {
if (!XRE_IsParentProcess()) {
return;
Expand All @@ -171,7 +185,8 @@ void IPCFuzzController::OnActorConnected(IProtocol* protocol) {
"DEBUG: IPCFuzzController::OnActorConnected() Mutex locked\n");

static bool protoIdFilterInitialized = false;
static bool allowNewActors = false;
static bool allowSubActors =
!!getenv("MOZ_FUZZ_PROTOID_FILTER_ALLOW_SUBACTORS");
static std::string protoIdFilter;
if (!protoIdFilterInitialized) {
const char* protoIdFilterStr = getenv("MOZ_FUZZ_PROTOID_FILTER");
Expand All @@ -191,25 +206,40 @@ void IPCFuzzController::OnActorConnected(IProtocol* protocol) {
}

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());
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;
} else if (actorIds[*portName].empty()) {
MOZ_FUZZING_NYX_PRINTF(
"INFO: [OnActorConnected] ActorID %d Protocol: %s is toplevel "
"actor.\n",
protocol->Id(), protocol->GetProtocolName());
} else if (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 (!!getenv("MOZ_FUZZ_DEBUG")) {
Expand All @@ -221,10 +251,12 @@ void IPCFuzzController::OnActorConnected(IProtocol* protocol) {

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;
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;
Expand Down Expand Up @@ -255,6 +287,16 @@ void IPCFuzzController::OnActorDestroyed(IProtocol* protocol) {
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() &&
Expand Down Expand Up @@ -331,8 +373,23 @@ bool IPCFuzzController::ObserveIPCMessage(mozilla::ipc::NodeChannel* channel,
}

lastActorPortName = event->port_name();
useLastPortName = true;
useLastActor = 1024;
useLastPortNameAlways = true;

if (!getenv("MOZ_FUZZ_PROTOID_FILTER_ALLOW_SUBACTORS")) {
// In this mode, we really want to focus on a single actor.
useLastActor = 1024;
maybeLastActorId = aMessage.routing_id();
MOZ_FUZZING_NYX_PRINTF("DEBUG: Pinned to actor %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);
}
}

// TODO: This is specific to PContent fuzzing. If we later want to fuzz
Expand Down Expand Up @@ -362,7 +419,7 @@ bool IPCFuzzController::ObserveIPCMessage(mozilla::ipc::NodeChannel* channel,
if (!channel->mBlockSendRecv) {
MOZ_FUZZING_NYX_PRINTF(
"INFO: [NodeChannel::OnMessageReceived] Blocking further "
"communication on Port %lu %lu (fuzzing started)\n",
"communication on node %lu %lu (fuzzing started)\n",
channel->GetName().v1, channel->GetName().v2);
channel->mBlockSendRecv = true;
}
Expand Down Expand Up @@ -437,7 +494,7 @@ bool IPCFuzzController::ObserveIPCMessage(mozilla::ipc::NodeChannel* channel,
if (!channel->mBlockSendRecv) {
MOZ_FUZZING_NYX_PRINTF(
"INFO: [NodeChannel::OnMessageReceived] Blocking further "
"communication on Port %lu %lu (fuzzing start pending)\n",
"communication on node %lu %lu (fuzzing start pending)\n",
channel->GetName().v1, channel->GetName().v2);
channel->mBlockSendRecv = true;
}
Expand Down Expand Up @@ -535,18 +592,6 @@ 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;
Expand All @@ -556,17 +601,26 @@ bool IPCFuzzController::MakeTargetDecision(
// 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;
}
if (!useLastActor && !useLastPortNameAlways && (portIndex & (1 << 7))) {
MOZ_FUZZING_NYX_PRINT(
"DEBUG: MakeTargetDecision: Released pinning on last port.\n");
useLastPortName = false;
}
} else if (useLastPortName) {
} else if (useLastPortName || useLastPortNameAlways) {
*name = lastActorPortName;
MOZ_FUZZING_NYX_PRINT("DEBUG: MakeTargetDecision: Pinned to last port.\n");
} else {
// Every possible toplevel actor type has a fixed number that
// we assign to it in the constructor of this class. Here, we
// use the lower 6 bits to select this toplevel actor type.
// This approach has the advantage that the tests will always
// select the same toplevel actor type deterministically,
// independent of the order they appeared and independent
// of the type of fuzzing we are doing.
auto portInstances = portNames[portIndex & 0x3f];
if (!portInstances.size()) {
return false;
}
*name = portInstances[portInstanceIndex % portInstances.size()];
}

Expand Down Expand Up @@ -595,7 +649,26 @@ bool IPCFuzzController::MakeTargetDecision(
bool isPreserveHeader = *type;

if (useLastActor) {
actorIndex = actors.size() - 1;
if (maybeLastActorId) {
bool found = false;
for (actorIndex = 0; actorIndex < actors.size(); ++actorIndex) {
// Toplevel actors have a discrepancy here: Routing ID is -1 but the
// actor id provided through protocol->Id() is 0.
if (actors[actorIndex].first == maybeLastActorId ||
(maybeLastActorId == MSG_ROUTING_CONTROL &&
!actors[actorIndex].first)) {
found = true;
break;
}
}

if (!found) {
MOZ_FUZZING_NYX_ABORT(
"ERROR: Pinned to actor that's not in actors map!?\n");
}
} else {
actorIndex = actors.size() - 1;
}
} else if (isPreserveHeader) {
// In preserveHeaderMode, we need to find an actor that matches the
// requested message type instead of any random actor.
Expand All @@ -607,6 +680,11 @@ bool IPCFuzzController::MakeTargetDecision(
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);
}
Expand All @@ -617,6 +695,14 @@ bool IPCFuzzController::MakeTargetDecision(
}

actorIndex = allowedIndices[actorIndex % allowedIndices.size()];
} else if (protoFilterTargetExcludeToplevel) {
// Filter out the toplevel protocol
if (actors.size() < 2) {
// We likely destroyed all other actors
return false;
}
actorIndex %= actors.size() - 1;
actorIndex++;
} else {
actorIndex %= actors.size();
}
Expand Down Expand Up @@ -1270,6 +1356,7 @@ UniquePtr<IPC::Message> IPCFuzzController::replaceIPCMessage(

IPCFuzzController::instance().useLastActor = 0;
IPCFuzzController::instance().useLastPortName = false;
IPCFuzzController::instance().useLastPortNameAlways = false;

MOZ_FUZZING_NYX_DEBUG("DEBUG: Requesting data...\n");

Expand Down
13 changes: 12 additions & 1 deletion tools/fuzzing/ipc/IPCFuzzController.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,24 @@ class IPCFuzzController {
// If set, `lastActorPortName` is valid and fuzzing is pinned to this port.
Atomic<bool> useLastPortName;

// Last port where a new actor appeared. Only valid with `useLastPortName`.
// If set, `lastActorPortName` is valid and fuzzing is forever pinned to this
// port.
Atomic<bool> useLastPortNameAlways;

// If set, the toplevel actor will be from fuzzing.
Atomic<bool> protoFilterTargetExcludeToplevel;

// Last port where a new actor appeared. Only valid with
// `useLastPortName`.
mojo::core::ports::PortName lastActorPortName;

// Counter to indicate how long fuzzing should stay pinned to the last
// actor that appeared on `lastActorPortName`.
Atomic<uint32_t> useLastActor;

// If this is non-zero, we want a specific actor ID instead of the last.
Atomic<int32_t> maybeLastActorId;

// This is the deterministic ordering of toplevel actors for fuzzing.
// In this matrix, each row (toplevel index) corresponds to one toplevel
// actor *type* while each entry in that row is an instance of that type,
Expand Down

0 comments on commit 17f7bf6

Please sign in to comment.