Repository navigation
Testing & tooling modernization: fuzzing, simulated network, zero warnings, parallel tests (+ security fixes) - #76
Merged
Conversation
FetchContent_Populate with declared details is deprecated (CMP0169) and slated for removal. Neither rnnoise nor its model archive has a CMakeLists.txt, so MakeAvailable just downloads them, as before. Also sets DOWNLOAD_EXTRACT_TIMESTAMP on the model archive (CMP0135).
Found by the new libFuzzer harnesses (Tests/Fuzz) and by reading the code around what they hit. Every issue is reachable by any connected peer. ReliabilityLayer split packets (every header field comes from the remote): - The first fragment of a split message preallocated one pointer per claimed fragment: a single datagram claiming 2^32 fragments made the receiver allocate 32 GB (remote denial of service). - Later fragments were not checked against the first fragment's count, so a fragment claiming a larger count and index was written past the fragment table (heap overflow in Release; only a RakAssert in Debug). - Reassembly sized its buffer from the total bit length but copied each fragment at a byte offset, so non-final fragments that were not whole bytes overran it. - The summed message length was 32-bit and could wrap. - A fragment rejected (or a duplicate) was freed by InsertIntoSplitPacketList and then read by the caller to look up its message: use-after-free. SplitPacketSort now stores fragments as they arrive (memory follows what was received, not what was claimed), rejects fragments that disagree with the first one or that are non-final and not whole bytes, and reassembly sums lengths in 64 bits. Datagram parsing no longer RakAsserts on malformed input, so a Debug build cannot be aborted remotely; it drops the packet, including one claiming more data than a datagram can hold. Reassembly is ~8% slower in the in-memory microbenchmark (std::map vs a flat array). BitStream: - Read(char*, n) and ReadAlignedBytes compared against n << 3 in 32 bits; lengths of 512 MB and up wrapped, passed the check and memcpy'd past the stream. - ReadAlignedBytesSafeAlloc returned its buffer allocated but unfilled when the read failed; callers that free only on success leaked it. - ReadDelta / ReadCompressedDelta branched on an uninitialized flag when the read hit the end of the stream.
…exposed mafianet_enable_warnings() applies -Wall -Wextra (/W4 on MSVC) to the MafiaNet libraries and test targets only, never to fetched dependencies. MAFIANET_WARNINGS_AS_ERRORS makes them errors. Apple Clang and GCC 13 now report zero warnings in Debug and Release. Real bugs behind the warnings, each fixed with a test where testable: - TCPInterface ConnectionAttemptLoop read s->bindAddress after deleting s (use-after-free on every outgoing TCP connect). - Table::Cell's copy constructor freed its own uninitialized pointer and left it uninitialized when the source held no data, so the copy's destructor freed garbage. - RelayPlugin relayed every group message as High / ReliableOrdered / channel 0, ignoring what the sender asked for. - The vsnprintf_s shim (Linux, macOS) wrote one byte past the buffer when _TRUNCATE truncated, which FormatString hit on any long string. - FileList::PopulateDataFromDisk(..., false, false, ...) never advanced to the next file and leaked the open handle: an infinite loop. - The test plugins' OnNewConnection / OnClosedConnection no longer matched PluginInterface2 and were never called. - RakVoice::SetOrderingChannels could not reject negative channels where char is unsigned (ARM). Also: fread/fgets results are checked (a short read skips the file, as an unopenable one did); sprintf -> snprintf where the bound is known; SystemAddress and RakNetGUID get explicit defaulted copy operations (their assignment operators were plain member-wise copies; the out-of-line SystemAddress::operator= is kept for the exported symbol); MSVC-only C4996 pragmas removed (the build already defines _CRT_SECURE_NO_DEPRECATE); dead code behind unused-but-set variables removed; mixed-indentation sites re-indented.
Fuzzing (Tests/Fuzz): harnesses for BitStream deserialization and for the datagram parser of a connected ReliabilityLayer, the code that parses what remote peers send. Each <name>_fuzz.cpp is built two ways: - <name>_replay, always with the tests, on every compiler: replays the checked-in corpus (seeds plus minimized crash reproducers) as the ctest case FuzzCorpus.<name>, label unit. A fixed crash stays fixed everywhere. - <name>_fuzz, a libFuzzer binary, with MAFIANET_BUILD_FUZZERS=ON (Clang). The fuzz preset builds them under ASan + UBSan with the library instrumented for coverage. Benchmarks (Benchmarks/, google/benchmark v1.9.5, MAFIANET_BUILD_BENCHMARKS, bench preset): BitStream serialization and the ReliabilityLayer send / ack / split path between two layers joined in memory. Coverage: MAFIANET_COVERAGE (--coverage) and the coverage preset; report with gcovr --root . --filter Source/ build/coverage.
…/macOS - fuzz: each libFuzzer harness for 60 s from the checked-in corpus under ASan + UBSan; a crash uploads its reproducer. - fuzz.yml (nightly, and on demand): 15 minutes per harness, with the grown corpus cached from night to night. - benchmarks: build and smoke-run the microbenchmarks, upload the JSON. Shared runners are too noisy to gate on numbers. - coverage: both suites under --coverage, gcovr summary in the job summary and the HTML report as an artifact. Informational. - linux-native, linux-minimal, macos and the sanitizer jobs build with MAFIANET_WARNINGS_AS_ERRORS. Windows (MSVC /W4) and the Ubuntu 22.04 Docker job keep warnings visible but non-fatal until measured. - Documents why the Docker job stays next to linux-native: it is the oldest supported toolchain (GCC 11, CMake 3.22).
Tests/Support/SimulatedNetwork drives real peers through connect, data transfer, MTU discovery, loss, reordering and timeouts with no sockets, no threads and a virtual clock, so those scenarios are deterministic, take milliseconds, and belong in the unit suite instead of loopback integration tests. Per-direction link rules model loss, latency, jitter, a path MTU or a dark link; randomness is a seeded LCG. Library seams, all opt-in and inert for normal use: - SetTimeSourceOverride (get_time.h): route GetTimeUS to a test clock. One atomic load per time query when unset. - RakNetSocket2Factory + RNS2T_USER (socket2.h): RakPeer::SetSocketFactory makes Startup take its sockets from a factory instead of binding OS sockets; IsBerkleySocket() is now false only for such sockets. - RakPeer::SetManualUpdate / RunManualUpdate: no internal update thread; the caller runs the update cycle. Shutdown(blockDuration) and GetSocket(s) work without the thread in that mode. Fixes found on the way: - RakNetSocket2 never initialized socketType outside AllocRNS2, so IsBerkleySocket() on any other subclass read garbage. - On Windows, Get64BitUniqueRandomNumber used only timer jitter, which collides for peers created back to back (and always under a virtual clock); it now mixes in random_device and a counter like POSIX. SimulatedPeerTests covers the handshake, ReliableOrdered under 20% loss with reordering, split reassembly, disconnect notification, timeout on a dark link, MTU discovery under a path ceiling, the incoming-connection limit, and run-to-run determinism.
peer_datagram drives a RakPeer server on the simulated network with datagrams from a remote that need not follow the protocol: offline messages, the handshake and, from the recorded session seed, everything a connected peer can send. Each input is a fresh, deterministic run.
Every integration test now binds ports the OS assigns (TestHelpers::StartOnLoopback / BoundPort; StandardServerPrep included), so RUN_SERIAL is gone and the suite runs under ctest -j: 74 tests in ~40 s locally with no retries, against several minutes serially. A taken port can no longer fail a test either (ComprehensiveConvert failed on a macOS runner that way, with Connect() returning INVALID_PARAMETER after an unchecked Startup). Every Startup() result is now asserted. The file-transfer tests also got per-run temp directories, which two parallel runs would otherwise share. Tests that check a peer's own addresses (LocalIsConnected, SystemAddressAndGuid) still bind all interfaces, on port 0. PeerBuilder's port-clash case gets a free port from the OS and then clashes on it.
…docs - ctest runs with --parallel in every job (2 under sanitizers), now that no test binds a fixed port. - Windows runs the integration suite too; it was build-only because of the shared fixed ports. - building.rst and CLAUDE.md document the warning policy, fuzzing (and adding crash reproducers to the corpus), benchmarks, coverage, the simulated network as the default home for peer-level tests, and the ephemeral-port rule.
…ability fuzz harness Constructing the per-datagram std::vector from an offset range made GCC 13 at -O2 warn about freeing a pointer with a nonzero offset, which -Werror turned into a build failure in the Linux Release job. An exactly sized heap copy keeps the ASan coverage and avoids the warning.
|
Important Review skippedToo many files! This PR contains 122 files, which is 22 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configuration
📒 Files selected for processing: 122
You can disable this status message by setting the
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- DOWNLOAD_EXTRACT_TIMESTAMP only exists from CMake 3.24; on 3.22 (the Ubuntu 22.04 job) it was read as part of URL_HASH and configure failed. Pass it only where supported. - SimulatedNetwork.cpp used std::back_inserter without <iterator>, which MSVC does not pull in transitively. - Coverage counters were updated concurrently by the network threads and came out negative, which gcovr rejects: build with -fprofile-update=atomic, and have gcovr warn rather than abort if it still sees one.
Dropping it from the lambda capture silenced Clang's unused-capture warning but MSVC requires a non-static local to be captured. A static constant needs no capture on either compiler.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Modernizes the testing and tooling setup. The new fuzzers and the warning cleanup turned up real bugs, including remotely triggerable memory-safety bugs, which are fixed here with regression tests. Commits are split by topic; the security fixes are in their own commit (
fix(security): …) so they can be reviewed or backported alone.Security fixes (any connected peer could trigger these)
Found by the new libFuzzer harnesses, or by reading the code around what they hit:
Read(char*, n)andReadAlignedBytescheckedn << 3in 32 bits. Lengths of 512 MB and up wrapped, passed the check, andmemcpyread past the stream.ReadAlignedBytesSafeAllocleaked its buffer when the read failed.ReadDeltabranched on an uninitialized flag.RakAsserts on malformed input. A Debug build could be aborted by whatever a peer sends.Zero compiler warnings, enforced
-Wall -Wextrais applied to MafiaNet's own targets only (mafianet_enable_warnings), and Apple Clang and GCC 13 are at zero in Debug and Release. The Linux, macOS and sanitizer CI jobs now build withMAFIANET_WARNINGS_AS_ERRORS. The cleanup exposed and fixed:TCPInterfaceon every outgoing TCP connect;Table::Cell's copy constructor freeing an uninitialized pointer;vsnprintf_sshim;FileList::PopulateDataFromDisk;RelayPluginignoring the sender's reliability, priority and channel for group messages;PluginInterface2and were never called;socketTypeleft uninitialized outsideAllocRNS2;New tooling
Tests/Fuzz, thefuzzpreset). Three harnesses:bit_stream,reliability_layer, andpeer_datagram(a whole server driven from offline messages through a connected session, seeded with a recorded real session).<name>_replayon every compiler, which replays the checked-in corpus, including the crash reproducers, as unit tests.fuzz.yml) with a corpus that persists between runs.Tests/Support/SimulatedNetwork). It runs wholeRakPeers with no sockets, no threads and a virtual clock: handshake, loss, reordering, MTU discovery, timeouts and limits are deterministic and take milliseconds, so they run in the unit suite.SetTimeSourceOverride,RakNetSocket2FactoryandRakPeer::SetManualUpdate. All are inert when unused.RUN_SERIALis gone. The suite takes about 40 s with-j8locally, and Windows now runs it too.Benchmarks/, google/benchmark v1.9.5, thebenchpreset), smoke-run in CI.coveragepreset): a gcovr summary in the job summary and an HTML artifact.FetchContent_MakeAvailable, replacing the deprecatedFetchContent_Populatethat a future CMake would remove.Trade-offs
std::mapinstead of a flat array). That's the cost of not trusting the claimed fragment count. Small messages are unchanged./W4warnings non-fatal until CI shows how many there are. MSVC wasn't measured locally.Verification
-j8) with no retries.-Werrorbuild cleanly, and all 338 tests pass.ci/tsan.supp.Not in this PR
.git-blame-ignore-revs).RakAsserts on wire input: others may remain deeper in the receive path. The fuzz builds use NDEBUG and can't see them.