yams/tools/fuzzing/meson.build
Trevon 6248c87bcc
Some checks are pending
Fuzz Smoke / libFuzzer smoke (push) Waiting to run
test(daemon): replay startup schedules under a fuzzer (#335)
Startup ordering defects were only observable by waiting minutes on a live
daemon, and the one that aborted it took a live restart loop to find. Give the
startup contracts a deterministic harness that runs in milliseconds.

+ startup_fuzz_support.h drives the real ServiceManagerFsm and the real
  DeferralGate with virtual time: legal phase advances, injected failure,
  shutdown and terminal reset, idle/busy, pressure/calm, starvation deadlines
  and cancellation
+ oracles fail closed on shutdown resurrection without reset, failure-to-ready,
  eligibility disagreeing with fsm.isReady(), admission outside the virtual
  deadline, and ineligible work touching the admission gate; fuzzed IEEE-754
  components must never yield non-finite or out-of-range scores
+ catch2_startup_replay runs named regressions plus 1024 seeded traces per run
  and reports the root seed and action offset for replay
+ fuzz_startup builds the same harness for libFuzzer/AFL, with a seed generator
  that refuses to overwrite different existing files
- the fuzzing lane no longer defaults to TSan: harnesses link
  -fsanitize=fuzzer,address,undefined and the two runtimes cannot share a
  process; combining them now fails with an explanation instead of producing a
  binary that cannot start
- documented the lane invocation, target naming and the coverage limits,
  including that governor call ordering inside the poller is not covered

Scope: contract-valid single-owner schedules. Executor interleavings, late
callbacks, concurrent FSM instances and filesystem/model faults are not modeled.

Signed-off-by: trvon <git@trevon.dev>
2026-10-22 16:48:50 -06:00

301 lines
10 KiB
Meson

# YAMS Fuzzing Harnesses
#
# Two engines share these targets (selected in the top-level meson.build):
# afl - AFL++ in libFuzzer compatibility mode (afl-clang-fast++, Docker):
# ./tools/fuzzing/fuzz.sh build
# libfuzzer - plain clang with the libFuzzer runtime; every project object is built with
# -fsanitize=fuzzer-no-link plus ASan/UBSan, harnesses link -fsanitize=fuzzer:
# CC=clang CXX=clang++ meson setup build/fuzzing -Dbuild-fuzzers=true -Db_lundef=false \
# -Dbuild-tests=false -Dbuild-plugins=false -Dbuild-cli=false
# meson compile -C build/fuzzing tools/fuzzing/fuzz_startup
# then ./tools/fuzzing/fuzz.sh local <target> (see fuzz.sh usage).
cpp = meson.get_compiler('cpp')
if yams_fuzz_engine == 'none'
warning('No fuzzing engine detected. Use afl-clang-fast++ (./tools/fuzzing/fuzz.sh build) '
+ 'or a clang that ships the libFuzzer runtime (e.g. Homebrew/apt LLVM).')
subdir_done()
endif
message('Fuzzing engine ' + yams_fuzz_engine + ' detected - building fuzzers')
# Fuzzers include Boost.Asio headers directly; ensure Boost include/link flags
# are present when compiling the harnesses (static libraries don't propagate
# include dirs to consumers).
fuzz_deps = [
boost_dep,
dependency('threads'),
dependency('zlib'),
dependency('OpenSSL'),
dependency('protobuf'),
dependency('nlohmann_json'),
]
if host_machine.system() == 'windows'
fuzz_deps += cpp.find_library('advapi32', required: true)
endif
# Seed generator (non-fuzzer utility)
seedgen = executable('seedgen',
'seedgen.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
build_by_default: true,
)
# AFL++ libFuzzer compatibility mode flags
# Per docs: "-fsanitize=fuzzer" provides automatic persistent mode. AFL applies ASan through
# AFL_USE_ASAN; plain libFuzzer builds link the sanitizer runtimes explicitly.
if yams_fuzz_engine == 'afl'
fuzzer_args = ['-fsanitize=fuzzer']
else
fuzzer_args = ['-fsanitize=fuzzer,address,undefined']
endif
# Controlled ABI plugin used by fuzz_plugin_abi_mount.
# This keeps dlopen() deterministic and avoids loading random fuzz bytes as shared libs.
fuzz_abi_test_plugin = shared_module('fuzz_abi_test_plugin',
'fuzz_abi_test_plugin.c',
include_directories: include_directories('../../include'),
c_args: ['-fPIC'],
install: false,
build_by_default: true,
)
fuzz_abi_negotiation_plugin = shared_module('fuzz_abi_negotiation_plugin',
'fuzz_abi_negotiation_plugin.c',
include_directories: include_directories('../../include'),
c_args: ['-fPIC'],
install: false,
build_by_default: true,
)
# Link against the daemon library built in this tree
# The daemon library contains MessageFramer and IPC protocol implementation
# Note: yams_daemon_lib is defined in src/daemon/meson.build and available in this scope
# Startup lifecycle/pressure schedules and semantic-score boundary. Shared replay support
# also runs under Catch2; this target adds the configured coverage-guided fuzzing engine.
fuzz_startup = executable('fuzz_startup',
'fuzz_startup.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
# The harness includes spdlog directly; link_with does not propagate that dependency's
# include paths or compile definitions (same reason as fuzz_deps above).
dependencies: fuzz_deps + [dependency('spdlog')],
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# IPC Protocol Fuzzer
fuzz_ipc_protocol = executable('fuzz_ipc_protocol',
'fuzz_ipc_protocol.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# Direct P2P protocol-v4 handshake/control fuzzer.
# The corpus retains protocol-v3 seeds to continuously exercise exact-version rejection.
fuzz_p2p_protocol = executable('fuzz_p2p_protocol',
'fuzz_p2p_protocol.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# Direct P2P delta/bootstrap control-message fuzzer.
fuzz_p2p_delta = executable('fuzz_p2p_delta',
'fuzz_p2p_delta.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# Direct P2P lane over the FrameSource seam: frame JSON, connection strings and pins, the
# handshake validators and acceptor, the delta receive paths with fuzzed session bounds, and the
# full manager inbound session. Each harness traps on an oracle violation.
foreach p2p_target : ['p2p_json', 'p2p_connstr', 'p2p_handshake', 'p2p_delta_stream',
'p2p_inbound']
executable('fuzz_' + p2p_target,
'fuzz_' + p2p_target + '.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
endforeach
# Topology snapshot decoder (untrusted persisted payloads).
fuzz_topology_codec = executable('fuzz_topology_codec',
'fuzz_topology_codec.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps + [dependency('yams_topology')],
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# FTS5 user-query sanitizer (MCP / CLI search text) against a real FTS5 table
fuzz_fts5_query = executable('fuzz_fts5_query',
'fuzz_fts5_query.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps + [sqlite_dep],
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# UTF-8 sanitizers + encoding detection/conversion on ingested bytes
fuzz_utf8 = executable('fuzz_utf8',
'fuzz_utf8.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# HTML / plain-text extractors, title and section detection on ingested bytes
fuzz_text_extractors = executable('fuzz_text_extractors',
'fuzz_text_extractors.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# Stored compressed blocks (header + zstd/LZMA payload) and compressor round-trips
fuzz_compressed_block = executable('fuzz_compressed_block',
'fuzz_compressed_block.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# AddDocumentRequest Fuzzer
fuzz_add_document = executable('fuzz_add_document',
'fuzz_add_document.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# ProtoSerializer Fuzzer
fuzz_proto_serializer = executable('fuzz_proto_serializer',
'fuzz_proto_serializer.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# RequestHandler Fuzzer
fuzz_request_handler = executable('fuzz_request_handler',
'fuzz_request_handler.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# StreamingRequestProcessor Fuzzer
fuzz_streaming_processor = executable('fuzz_streaming_processor',
'fuzz_streaming_processor.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# IPC Roundtrip Fuzzer (CLI<->daemon framing + payload)
fuzz_ipc_roundtrip = executable('fuzz_ipc_roundtrip',
'fuzz_ipc_roundtrip.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# Plugin Trust/Path Fuzzer (trust file parsing + safe path containment)
fuzz_plugin_trust = executable('fuzz_plugin_trust',
'fuzz_plugin_trust.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# ABI plugin mount fuzzer (loads controlled test plugin .so through AbiPluginLoader)
fuzz_plugin_abi_mount = executable('fuzz_plugin_abi_mount',
'fuzz_plugin_abi_mount.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# ABI interface/manifest negotiation fuzzer
fuzz_plugin_abi_negotiation = executable('fuzz_plugin_abi_negotiation',
'fuzz_plugin_abi_negotiation.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
# Daemon download job IPC fuzzer
fuzz_download_jobs = executable('fuzz_download_jobs',
'fuzz_download_jobs.cpp',
include_directories: include_directories('../../include'),
link_with: yams_daemon_lib,
dependencies: fuzz_deps,
cpp_args: fuzzer_args,
link_args: fuzzer_args,
build_by_default: true,
)
if yams_fuzz_engine == 'afl'
message('Fuzzing harnesses configured with AFL++ libFuzzer mode')
message('ASAN enabled via AFL_USE_ASAN=1 environment variable')
else
message('Fuzzing harnesses configured with libFuzzer + ASan/UBSan')
endif