345 lines
11 KiB
C++
345 lines
11 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Structured seed generator for IPC fuzzers.
|
|
|
|
#include <yams/daemon/ipc/ipc_protocol.h>
|
|
#include <yams/daemon/ipc/message_framing.h>
|
|
#include <yams/daemon/ipc/proto_serializer.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
using namespace yams::daemon;
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace {
|
|
|
|
struct Args {
|
|
fs::path outRoot;
|
|
uint32_t maxSeeds = 40;
|
|
};
|
|
|
|
bool parseArgs(int argc, char** argv, Args& out) {
|
|
for (int i = 1; i < argc; ++i) {
|
|
std::string_view arg(argv[i]);
|
|
if (arg == "--out" && i + 1 < argc) {
|
|
out.outRoot = fs::path(argv[++i]);
|
|
} else if (arg == "--max-seeds" && i + 1 < argc) {
|
|
out.maxSeeds = static_cast<uint32_t>(std::stoul(argv[++i]));
|
|
} else if (arg == "--help" || arg == "-h") {
|
|
return false;
|
|
} else {
|
|
std::cerr << "Unknown arg: " << arg << "\n";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (out.outRoot.empty()) {
|
|
std::cerr << "Missing required --out <dir>\n";
|
|
return false;
|
|
}
|
|
if (out.maxSeeds == 0) {
|
|
out.maxSeeds = 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool writeFile(const fs::path& path, std::span<const uint8_t> bytes) {
|
|
std::ofstream out(path, std::ios::binary);
|
|
if (!out.is_open()) {
|
|
return false;
|
|
}
|
|
out.write(reinterpret_cast<const char*>(bytes.data()),
|
|
static_cast<std::streamsize>(bytes.size()));
|
|
return static_cast<bool>(out);
|
|
}
|
|
|
|
std::vector<uint8_t> flipOneByte(std::vector<uint8_t> bytes) {
|
|
if (!bytes.empty()) {
|
|
bytes[bytes.size() / 2] ^= 0xA5;
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
Message makeMsg(uint64_t requestId, Request req) {
|
|
Message msg{};
|
|
msg.version = 1;
|
|
msg.requestId = requestId;
|
|
msg.payload = std::move(req);
|
|
msg.expectsStreamingResponse = false;
|
|
return msg;
|
|
}
|
|
|
|
std::vector<Message> buildStructuredMessages() {
|
|
std::vector<Message> msgs;
|
|
uint64_t id = 1;
|
|
|
|
// SearchRequest edge cases
|
|
{
|
|
SearchRequest r{};
|
|
r.query = "";
|
|
r.limit = 0;
|
|
r.fuzzy = false;
|
|
r.literalText = true;
|
|
r.similarity = 0.0;
|
|
r.timeout = std::chrono::milliseconds{0};
|
|
r.searchType = "keyword";
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<SearchRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
SearchRequest r{};
|
|
r.query = " ";
|
|
r.limit = 1;
|
|
r.fuzzy = true;
|
|
r.literalText = false;
|
|
r.similarity = 1.0;
|
|
r.timeout = std::chrono::milliseconds{1};
|
|
r.searchType = "semantic";
|
|
r.pathPatterns = {"**/*.cpp", "**/*.hpp", "**/*"};
|
|
r.tags = {"", "pinned", "pinned"};
|
|
r.matchAllTags = true;
|
|
r.afterContext = 3;
|
|
r.beforeContext = 3;
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<SearchRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
SearchRequest r{};
|
|
r.query = std::string(4096, 'A');
|
|
r.limit = static_cast<size_t>(std::numeric_limits<uint32_t>::max());
|
|
r.fuzzy = true;
|
|
r.literalText = true;
|
|
r.similarity = std::numeric_limits<double>::quiet_NaN();
|
|
r.timeout = std::chrono::milliseconds{-1};
|
|
r.searchType = "hybrid";
|
|
r.extension = ".txt";
|
|
r.mimeType = "text/plain";
|
|
r.textOnly = true;
|
|
r.binaryOnly = true; // conflicting on purpose
|
|
r.vectorStageTimeoutMs = -1;
|
|
r.keywordStageTimeoutMs = 0;
|
|
r.snippetHydrationTimeoutMs = 1;
|
|
r.useSession = true;
|
|
r.sessionName = "sess";
|
|
r.instanceId = "instance-00000000-0000-0000-0000-000000000000";
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<SearchRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
SearchRequest r{};
|
|
r.query = std::string("nul\0byte", 8);
|
|
r.limit = 20;
|
|
r.fuzzy = false;
|
|
r.literalText = false;
|
|
r.similarity = std::numeric_limits<double>::infinity();
|
|
r.timeout = std::chrono::milliseconds{5000};
|
|
r.searchType = "unknown-type";
|
|
r.pathsOnly = true;
|
|
r.jsonOutput = true;
|
|
r.showLineNumbers = true;
|
|
r.context = -1;
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<SearchRequest>, std::move(r)}));
|
|
}
|
|
|
|
// GrepRequest edge cases
|
|
{
|
|
GrepRequest r{};
|
|
r.pattern = "";
|
|
r.path = "";
|
|
r.paths = {};
|
|
r.caseInsensitive = false;
|
|
r.invertMatch = false;
|
|
r.contextLines = 0;
|
|
r.maxMatches = 0;
|
|
r.recursive = true;
|
|
r.literalText = true;
|
|
r.regexOnly = false;
|
|
r.semanticLimit = 0;
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<GrepRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
GrepRequest r{};
|
|
r.pattern = "(.+)+";
|
|
r.path = "./";
|
|
r.paths = {".", "..", "src", "include"};
|
|
r.caseInsensitive = true;
|
|
r.invertMatch = true;
|
|
r.contextLines = -1;
|
|
r.maxMatches = static_cast<size_t>(std::numeric_limits<uint64_t>::max());
|
|
r.includePatterns = {"**/*.cpp", "**/*.h", "**/*.md"};
|
|
r.recursive = false;
|
|
r.wholeWord = true;
|
|
r.showLineNumbers = true;
|
|
r.showFilename = true;
|
|
r.noFilename = true; // conflicting
|
|
r.countOnly = true;
|
|
r.filesOnly = true;
|
|
r.filesWithoutMatch = true; // conflicting
|
|
r.pathsOnly = true;
|
|
r.literalText = false;
|
|
r.regexOnly = true;
|
|
r.semanticLimit = static_cast<size_t>(std::numeric_limits<uint64_t>::max());
|
|
r.filterTags = {"pinned", "", "fuzz"};
|
|
r.matchAllTags = true;
|
|
r.colorMode = "always";
|
|
r.beforeContext = std::numeric_limits<int>::min();
|
|
r.afterContext = std::numeric_limits<int>::max();
|
|
r.showDiff = true;
|
|
r.useSession = true;
|
|
r.sessionName = "sess";
|
|
r.instanceId = "instance-11111111-1111-1111-1111-111111111111";
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<GrepRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
GrepRequest r{};
|
|
r.pattern = std::string(2048, 'B');
|
|
r.path = "src/daemon/ipc";
|
|
r.paths = {"src/daemon/ipc/proto_serializer.cpp"};
|
|
r.caseInsensitive = false;
|
|
r.invertMatch = false;
|
|
r.contextLines = 999999;
|
|
r.maxMatches = 1;
|
|
r.includePatterns = {"**/*"};
|
|
r.recursive = true;
|
|
r.literalText = true;
|
|
r.regexOnly = false;
|
|
r.semanticLimit = 10;
|
|
r.colorMode = "never";
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<GrepRequest>, std::move(r)}));
|
|
}
|
|
|
|
// DeleteRequest edge cases
|
|
{
|
|
DeleteRequest r{};
|
|
r.hash = "";
|
|
r.purge = false;
|
|
r.name = "";
|
|
r.force = false;
|
|
r.dryRun = true;
|
|
r.keepRefs = true;
|
|
r.recursive = false;
|
|
r.verbose = true;
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<DeleteRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
DeleteRequest r{};
|
|
r.hash = "deadbeef";
|
|
r.purge = true;
|
|
r.name = "../../etc/passwd";
|
|
r.names = {"a", "b", "..", "./x", std::string(256, 'C')};
|
|
r.pattern = "**/*";
|
|
r.directory = "/tmp";
|
|
r.force = true;
|
|
r.dryRun = false;
|
|
r.keepRefs = false;
|
|
r.recursive = true;
|
|
r.verbose = true;
|
|
r.sessionId = "sess";
|
|
r.instanceId = "instance-22222222-2222-2222-2222-222222222222";
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<DeleteRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
DeleteRequest r{};
|
|
r.hash = std::string(64, 'f');
|
|
r.purge = false;
|
|
r.pattern = "["; // malformed glob-ish
|
|
r.directory = "";
|
|
r.force = true;
|
|
r.dryRun = true;
|
|
r.recursive = true;
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<DeleteRequest>, std::move(r)}));
|
|
}
|
|
|
|
// A couple of simple baseline requests
|
|
{
|
|
PingRequest r{};
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<PingRequest>, std::move(r)}));
|
|
}
|
|
{
|
|
StatusRequest r{};
|
|
msgs.push_back(makeMsg(id++, Request{std::in_place_type<StatusRequest>, std::move(r)}));
|
|
}
|
|
|
|
return msgs;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
Args args;
|
|
if (!parseArgs(argc, argv, args)) {
|
|
std::cerr << "Usage: seedgen --out <dir> [--max-seeds N]\n";
|
|
return 2;
|
|
}
|
|
|
|
const fs::path ipcProtocolDir = args.outRoot / "ipc_protocol";
|
|
const fs::path ipcRoundtripDir = args.outRoot / "ipc_roundtrip";
|
|
const fs::path requestHandlerDir = args.outRoot / "request_handler";
|
|
const fs::path protoSerializerDir = args.outRoot / "proto_serializer";
|
|
|
|
fs::create_directories(ipcProtocolDir);
|
|
fs::create_directories(ipcRoundtripDir);
|
|
fs::create_directories(requestHandlerDir);
|
|
fs::create_directories(protoSerializerDir);
|
|
|
|
MessageFramer framer;
|
|
auto msgs = buildStructuredMessages();
|
|
|
|
uint32_t written = 0;
|
|
auto emit = [&](std::string_view stem, const Message& msg) {
|
|
if (written >= args.maxSeeds) {
|
|
return;
|
|
}
|
|
|
|
auto payloadRes = ProtoSerializer::encode_payload(msg);
|
|
if (payloadRes) {
|
|
auto payload = std::move(payloadRes.value());
|
|
const auto outPath = protoSerializerDir / (std::string(stem) + "_payload.bin");
|
|
if (writeFile(outPath, payload)) {
|
|
++written;
|
|
}
|
|
}
|
|
|
|
auto frameRes = framer.frame_message(msg);
|
|
if (frameRes) {
|
|
auto frame = std::move(frameRes.value());
|
|
|
|
const auto out1 = ipcProtocolDir / (std::string(stem) + "_frame.bin");
|
|
const auto out2 = ipcRoundtripDir / (std::string(stem) + "_frame.bin");
|
|
const auto out3 = requestHandlerDir / (std::string(stem) + "_frame.bin");
|
|
(void)writeFile(out1, frame);
|
|
(void)writeFile(out2, frame);
|
|
(void)writeFile(out3, frame);
|
|
|
|
// Mutations that preserve overall shape but exercise error paths
|
|
auto badCrc = flipOneByte(frame);
|
|
(void)writeFile(ipcProtocolDir / (std::string(stem) + "_badcrc.bin"), badCrc);
|
|
(void)writeFile(ipcRoundtripDir / (std::string(stem) + "_badcrc.bin"), badCrc);
|
|
(void)writeFile(requestHandlerDir / (std::string(stem) + "_badcrc.bin"), badCrc);
|
|
}
|
|
|
|
auto chunkRes = framer.frame_message_chunk(msg, /*last_chunk=*/true);
|
|
if (chunkRes) {
|
|
auto chunk = std::move(chunkRes.value());
|
|
(void)writeFile(ipcProtocolDir / (std::string(stem) + "_chunk_last.bin"), chunk);
|
|
(void)writeFile(ipcRoundtripDir / (std::string(stem) + "_chunk_last.bin"), chunk);
|
|
(void)writeFile(requestHandlerDir / (std::string(stem) + "_chunk_last.bin"), chunk);
|
|
}
|
|
};
|
|
|
|
uint32_t idx = 0;
|
|
for (const auto& msg : msgs) {
|
|
const std::string stem = "llm_" + std::to_string(idx++);
|
|
emit(stem, msg);
|
|
}
|
|
|
|
std::cout << "Wrote structured seeds (max=" << args.maxSeeds << ") into "
|
|
<< args.outRoot.string() << "\n";
|
|
return 0;
|
|
}
|