mirror of
https://github.com/meshtastic/firmware.git
synced 2025-02-01 02:09:57 +00:00
b0fe5ef8ba
* Initial commit of a fuzzer for Meshtastic. * Use a max of 5 for the phone queues * Only write files to the temp dir * Limitless queue + fuzzer = lots of ram :) * Use $PIO_ENV for path to program * spelling: s/is/to/ * Use loopCanSleep instead of a lock in Router * realHardware allows full use of a CPU core * Ignore checkov CKV_DOCKER_2 & CKV_DOCKER_3 * Add Atak seed * Fix lint issues in build.sh * Use exception to exit from portduino_main * Separate build & source files into $WORK & $SRC * Use an ephemeral port for the API server * Include CXXFLAGS in the link step * Read all shared libraries * Use a separate work directory for each sanitizer --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
206 lines
8.0 KiB
C++
206 lines
8.0 KiB
C++
// Fuzzer implementation that sends MeshPackets to Router::enqueueReceivedMessage.
|
|
#include <condition_variable>
|
|
#include <cstdlib>
|
|
#include <mutex>
|
|
#include <pb_decode.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "PortduinoGPIO.h"
|
|
#include "PortduinoGlue.h"
|
|
#include "PowerFSM.h"
|
|
#include "mesh/MeshTypes.h"
|
|
#include "mesh/NodeDB.h"
|
|
#include "mesh/Router.h"
|
|
#include "mesh/TypeConversions.h"
|
|
#include "mesh/mesh-pb-constants.h"
|
|
|
|
namespace
|
|
{
|
|
constexpr uint32_t nodeId = 0x12345678;
|
|
// Set to true when lateInitVariant finishes. Used to ensure lateInitVariant was called during startup.
|
|
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 runLoopOnce() 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.
|
|
bool loopShouldExit = false; // Indicates that the main Arduino thread should exit by throwing ShouldExitException.
|
|
std::mutex loopLock;
|
|
std::condition_variable loopCV;
|
|
std::thread meshtasticThread;
|
|
|
|
// This exception is thrown when the portuino main thread should exit.
|
|
class ShouldExitException : public std::runtime_error
|
|
{
|
|
public:
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
|
|
// 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()
|
|
{
|
|
realHardware = true; // Avoids delay(100) within portduino/main.cpp
|
|
std::unique_lock<std::mutex> lck(loopLock);
|
|
fuzzerRunning = true;
|
|
loopCanRun = true;
|
|
loopCV.notify_one();
|
|
loopCV.wait(lck, [] { return !loopCanRun && loopIsWaiting; });
|
|
}
|
|
} // namespace
|
|
|
|
// 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 || loopShouldExit; });
|
|
loopIsWaiting = false;
|
|
if (loopShouldExit)
|
|
throw ShouldExitException("exit");
|
|
if (!fuzzerRunning)
|
|
return true; // The loop can sleep before the fuzzer starts.
|
|
loopCanRun = false; // Only run the loop once before waiting again.
|
|
return false;
|
|
}
|
|
|
|
// Called just prior to starting Meshtastic. Allows for setting config values before startup.
|
|
void lateInitVariant()
|
|
{
|
|
settingsMap[logoutputlevel] = level_error;
|
|
channelFile.channels[0] = meshtastic_Channel{
|
|
.has_settings = true,
|
|
.settings =
|
|
meshtastic_ChannelSettings{
|
|
.psk = {.size = 1, .bytes = {/*defaultpskIndex=*/1}},
|
|
.name = "LongFast",
|
|
.uplink_enabled = true,
|
|
.has_module_settings = true,
|
|
.module_settings = {.position_precision = 16},
|
|
},
|
|
.role = meshtastic_Channel_Role_PRIMARY,
|
|
};
|
|
config.security.admin_key[0] = {
|
|
.size = 32,
|
|
.bytes = {0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a,
|
|
0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c},
|
|
};
|
|
config.security.admin_key_count = 1;
|
|
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
|
|
moduleConfig.has_mqtt = true;
|
|
moduleConfig.mqtt = meshtastic_ModuleConfig_MQTTConfig{
|
|
.enabled = true,
|
|
.proxy_to_client_enabled = true,
|
|
};
|
|
moduleConfig.has_store_forward = true;
|
|
moduleConfig.store_forward = meshtastic_ModuleConfig_StoreForwardConfig{
|
|
.enabled = true,
|
|
.history_return_max = 4,
|
|
.history_return_window = 600,
|
|
.is_server = true,
|
|
};
|
|
meshtastic_Position fixedGPS = meshtastic_Position{
|
|
.has_latitude_i = true,
|
|
.latitude_i = static_cast<uint32_t>(1 * 1e7),
|
|
.has_longitude_i = true,
|
|
.longitude_i = static_cast<uint32_t>(3 * 1e7),
|
|
.has_altitude = true,
|
|
.altitude = 64,
|
|
.location_source = meshtastic_Position_LocSource_LOC_MANUAL,
|
|
};
|
|
nodeDB->setLocalPosition(fixedGPS);
|
|
config.has_position = true;
|
|
config.position.fixed_position = true;
|
|
meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
info->has_position = true;
|
|
info->position = TypeConversions::ConvertToPositionLite(fixedGPS);
|
|
hasBeenConfigured = true;
|
|
}
|
|
|
|
extern "C" {
|
|
int portduino_main(int argc, char **argv); // Renamed "main" function from Meshtastic binary.
|
|
|
|
// Start Meshtastic in a thread and wait till it has reached the ON state.
|
|
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
|
{
|
|
settingsMap[maxtophone] = 5;
|
|
|
|
meshtasticThread = std::thread([program = *argv[0]]() {
|
|
char nodeIdStr[12];
|
|
strcpy(nodeIdStr, std::to_string(nodeId).c_str());
|
|
int argc = 7;
|
|
char *argv[] = {program, "-d", "/tmp/meshtastic", "-h", nodeIdStr, "-p", "0", nullptr};
|
|
try {
|
|
portduino_main(argc, argv);
|
|
} catch (const ShouldExitException &) {
|
|
}
|
|
});
|
|
std::atexit([] {
|
|
{
|
|
const std::lock_guard<std::mutex> lck(loopLock);
|
|
loopShouldExit = true;
|
|
loopCV.notify_one();
|
|
}
|
|
meshtasticThread.join();
|
|
});
|
|
|
|
// Wait for startup.
|
|
for (int i = 1; i < 20; ++i) {
|
|
if (powerFSM.getState() == &stateON) {
|
|
assert(hasBeenConfigured);
|
|
assert(router);
|
|
assert(nodeDB);
|
|
return 0;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// This is the main entrypoint for the fuzzer (the fuzz target). The fuzzer will provide an array of bytes to be
|
|
// interpreted by this method. To keep things simple, the bytes are interpreted as a binary serialized MeshPacket
|
|
// proto. Any crashes discovered by the fuzzer will be written to a file. Unserialize that file to print the MeshPacket
|
|
// that caused the failure.
|
|
//
|
|
// This guide provides best practices for writing a fuzzer target.
|
|
// https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md
|
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t length)
|
|
{
|
|
meshtastic_MeshPacket p = meshtastic_MeshPacket_init_default;
|
|
pb_istream_t stream = pb_istream_from_buffer(data, length);
|
|
// Ignore any inputs that fail to decode or have fields set that are not transmitted over LoRa.
|
|
if (!pb_decode(&stream, &meshtastic_MeshPacket_msg, &p) || p.rx_time || p.rx_snr || p.priority || p.rx_rssi || p.delayed ||
|
|
p.public_key.size || p.next_hop || p.relay_node || p.tx_after)
|
|
return -1; // Reject: The input will not be added to the corpus.
|
|
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
|
meshtastic_Data d;
|
|
stream = pb_istream_from_buffer(p.decoded.payload.bytes, p.decoded.payload.size);
|
|
if (!pb_decode(&stream, &meshtastic_Data_msg, &d))
|
|
return -1; // Reject: The input will not be added to the corpus.
|
|
}
|
|
|
|
// Provide default values for a few fields so the fuzzer doesn't need to guess them.
|
|
if (p.from == 0)
|
|
p.from = nodeDB->getNodeNum();
|
|
if (p.to == 0)
|
|
p.to = nodeDB->getNodeNum();
|
|
static uint32_t packetId = 0;
|
|
if (p.id == 0)
|
|
p.id == ++packetId;
|
|
if (p.pki_encrypted && config.security.admin_key_count)
|
|
memcpy(&p.public_key, &config.security.admin_key[0], sizeof(p.public_key));
|
|
|
|
router->enqueueReceivedMessage(packetPool.allocCopy(p));
|
|
runLoopOnce();
|
|
return 0; // Accept: The input may be added to the corpus.
|
|
}
|
|
} |