New libFuzzer/AFL++ harnesses, each trapping on an oracle violation: p2p_json (nesting bound, round trip), p2p_connstr (endpoint round trip, pin idempotence, no silent re-pin), p2p_handshake (validators and the full acceptor bind the TLS peer), p2p_delta_stream (batch, session and bootstrap receive paths stay inside count and byte budgets), p2p_inbound (the manager inbound session through registry update) and topology_codec (decoded snapshots re-encode losslessly). p2p_delta now draws its exchange limits from the input. p2p_seeds.py writes framed-session seeds for FuzzedDataProvider layouts, dicts/p2p.dict carries the frame vocabulary, generate_corpus.sh accepts a local seedgen, and fuzz.sh no longer fails on macOS bash 3.2 when a target has no dictionary. The generated corpus stays untracked.
76 lines
2.8 KiB
C++
76 lines
2.8 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright 2026 YAMS Contributors
|
|
|
|
// The P2pManager inbound session (handshake -> history -> delta exchange -> history -> registry)
|
|
// replayed from a length-prefixed frame stream against a fresh registry and memory-sync service.
|
|
// Oracles: a session that completes has pinned the TLS peer, and the stream is never over-read.
|
|
|
|
#include "p2p_fuzz_support.h"
|
|
|
|
// pi-lens-ignore: fatal error
|
|
#include <yams/daemon/p2p/peer_registry.h>
|
|
|
|
#include <fuzzer/FuzzedDataProvider.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <string>
|
|
#include <unistd.h>
|
|
|
|
namespace {
|
|
|
|
std::filesystem::path registryPath() {
|
|
// One registry file per fuzzing process; it is recreated for every input.
|
|
static const auto path = std::filesystem::temp_directory_path() /
|
|
("yams-fuzz-p2p-inbound-" + std::to_string(::getpid()) + ".db");
|
|
return path;
|
|
}
|
|
|
|
void removeRegistry() {
|
|
std::error_code ec;
|
|
for (const auto* suffix : {"", "-wal", "-shm", "-journal"}) {
|
|
std::filesystem::remove(registryPath().string() + suffix, ec);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) {
|
|
using namespace yams::daemon::p2p;
|
|
using yams::fuzzing::fuzzRequire;
|
|
if (data == nullptr || size > 4 * kP2pMaxControlFrameBytes) {
|
|
return 0;
|
|
}
|
|
FuzzedDataProvider input(data, size);
|
|
detail::InboundSessionOptions options;
|
|
options.nodeId = std::string(yams::fuzzing::kFuzzLocalNodeId);
|
|
options.corpusId = std::string(yams::fuzzing::kFuzzCorpusId);
|
|
options.corpusEpoch = yams::fuzzing::kFuzzCorpusEpoch;
|
|
options.allowFirstContact = input.ConsumeBool();
|
|
options.timeout = std::chrono::milliseconds(50);
|
|
|
|
removeRegistry();
|
|
auto registry = PeerRegistry::open(registryPath(), 4);
|
|
if (!registry) {
|
|
return 0;
|
|
}
|
|
auto service = yams::fuzzing::makeMemorySyncService();
|
|
const auto identity = yams::fuzzing::peerChannelIdentity();
|
|
const auto stream = input.ConsumeRemainingBytes<std::uint8_t>();
|
|
detail::BufferFrameSource frames(yams::fuzzing::asBytes(stream.data(), stream.size()));
|
|
|
|
const auto outcome =
|
|
detail::runInboundSession(frames, identity, *service, *registry.value(), options);
|
|
fuzzRequire(frames.consumedBytes() <= stream.size());
|
|
if (outcome.result && outcome.stage == detail::InboundSessionStage::RegistryUpdate) {
|
|
// Completing the session means the unknown peer was pinned under first-contact trust.
|
|
fuzzRequire(options.allowFirstContact);
|
|
auto pinned =
|
|
registry.value()->verifyOrPin(identity.peerCertCn, identity.peerSpkiPin, false);
|
|
fuzzRequire(pinned.has_value());
|
|
}
|
|
registry.value().reset();
|
|
removeRegistry();
|
|
return 0;
|
|
}
|