Use loopCanSleep instead of a lock in Router

This commit is contained in:
Eric Severance 2025-01-08 17:06:42 -08:00
parent 08c82b53b7
commit 091b174e2b
No known key found for this signature in database
GPG Key ID: F19AABB5E1EA1B5F
3 changed files with 41 additions and 20 deletions

View File

@ -1,4 +1,5 @@
// Fuzzer implementation that sends MeshPackets to Router::enqueueReceivedMessage. // Fuzzer implementation that sends MeshPackets to Router::enqueueReceivedMessage.
#include <condition_variable>
#include <mutex> #include <mutex>
#include <pb_decode.h> #include <pb_decode.h>
#include <string> #include <string>
@ -15,7 +16,30 @@
namespace namespace
{ {
constexpr uint32_t nodeId = 0x12345678; constexpr uint32_t nodeId = 0x12345678;
// Set to true when lateInitVariant finishes. Used to ensure lateInitVariant was called during startup.
bool hasBeenConfigured = false; bool hasBeenConfigured = false;
// These are used to block the Arduino loop() function until a fuzzer input is ready. This is
// an optimization that prevents a sleep from happening before the loop is run. The Arduino loop
// function calls loopCanSleep() before sleeping. loopCanSleep is implemented here in the fuzzer
// and blocks until startLoop() is called to signal for the loop to run.
bool fuzzerRunning = false; // Set to true once LLVMFuzzerTestOneInput has started running.
bool loopCanRun = true; // The main Arduino loop() can run when this is true.
bool loopIsWaiting = false; // The main Arduino loop() is waiting to be signaled to run.
std::mutex loopLock;
std::condition_variable loopCV;
// Start the loop for one test case and wait till the loop has completed. This ensures fuzz
// test cases do not overlap with one another. This helps the fuzzer attribute a crash to the
// single, currently running, test case.
void runLoopOnce()
{
std::unique_lock<std::mutex> lck(loopLock);
fuzzerRunning = true;
loopCanRun = true;
loopCV.notify_one();
loopCV.wait(lck, [] { return !loopCanRun && loopIsWaiting; });
}
} // namespace } // namespace
// Called just prior to starting Meshtastic. Allows for setting config values before startup. // Called just prior to starting Meshtastic. Allows for setting config values before startup.
@ -71,6 +95,22 @@ void lateInitVariant()
hasBeenConfigured = true; hasBeenConfigured = true;
} }
// Called in the main Arduino loop function to determine if the loop can delay/sleep before running again.
// We use this as a way to block the loop from sleeping and to start the loop function immediately when a
// fuzzer input is ready.
bool loopCanSleep()
{
std::unique_lock<std::mutex> lck(loopLock);
loopIsWaiting = true;
loopCV.notify_one();
loopCV.wait(lck, [] { return loopCanRun; });
loopIsWaiting = false;
if (!fuzzerRunning)
return true; // The loop can sleep before the fuzzer starts.
loopCanRun = false; // Only run the loop once before waiting again.
return false;
}
extern "C" { extern "C" {
int portduino_main(int argc, char **argv); // Renamed "main" function from Meshtastic binary. int portduino_main(int argc, char **argv); // Renamed "main" function from Meshtastic binary.
@ -134,13 +174,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t length)
if (p.pki_encrypted && config.security.admin_key_count) if (p.pki_encrypted && config.security.admin_key_count)
memcpy(&p.public_key, &config.security.admin_key[0], sizeof(p.public_key)); memcpy(&p.public_key, &config.security.admin_key[0], sizeof(p.public_key));
// Ideally only one packet, the one generated by the fuzzer, is being processed by the firmware at
// a time. We acquire a lock here, and the router unlocks it after it has processed all queued packets.
// Grabbing the lock again, below, should block until the queue has been emptied.
router->inProgressLock.lock();
router->enqueueReceivedMessage(packetPool.allocCopy(p)); router->enqueueReceivedMessage(packetPool.allocCopy(p));
runLoopOnce();
const std::lock_guard<std::mutex> lck(router->inProgressLock);
return 0; // Accept: The input may be added to the corpus. return 0; // Accept: The input may be added to the corpus.
} }
} }

View File

@ -70,10 +70,6 @@ int32_t Router::runOnce()
perhapsHandleReceived(mp); perhapsHandleReceived(mp);
} }
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
inProgressLock.unlock();
return 5;
#endif
// LOG_DEBUG("Sleep forever!"); // LOG_DEBUG("Sleep forever!");
return INT32_MAX; // Wait a long time - until we get woken for the message queue return INT32_MAX; // Wait a long time - until we get woken for the message queue
} }

View File

@ -8,10 +8,6 @@
#include "RadioInterface.h" #include "RadioInterface.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#include <mutex>
#endif
/** /**
* A mesh aware router that supports multiple interfaces. * A mesh aware router that supports multiple interfaces.
*/ */
@ -90,12 +86,6 @@ class Router : protected concurrency::OSThread
before us */ before us */
uint32_t rxDupe = 0, txRelayCanceled = 0; uint32_t rxDupe = 0, txRelayCanceled = 0;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
// Used by router_fuzzer.cpp to detect when the Router has finished processing a packet.
// See LLVMFuzzerTestOneInput in router_fuzzer.cpp & Router::runOnce for how this is used.
std::mutex inProgressLock;
#endif
protected: protected:
friend class RoutingModule; friend class RoutingModule;