目录

Packet Codec: Static Analysis and Fuzzing Lab

A C++17 binary protocol implementation for studying untrusted lengths, checksum validation, incremental parsing, fragment lifetimes, and state-machine testing. The core library depends only on the C++ standard library.

Status

The library implementation, CLI, and two libFuzzer targets are present. This is an unverified source draft: no build, test, static-analysis, or Fuzz result is claimed. The current CMakeLists.txt references tests/test_packet.cpp, which has not yet been written. Complete that test source or remove that test target before using the build commands below. BUILD_TESTING=OFF alone does not remove the current unconditional test executable declaration.

Seed and artifact directories in the examples are created by the displayed commands. They are not a claim that a prepared binary corpus is already bundled.

Features and Layout

Component Responsibility
include/packet/bytes.hpp, src/bytes.cpp Bounded views, readers/writers, integer encoding, CRC32, hex, UTF-8, file I/O
include/packet/codec.hpp, src/codec.cpp Frame headers, TLV fields, encoding, decoding, and field validation
include/packet/stream.hpp, src/stream.cpp Incremental frame decoding and optional alignment recovery
src/message.cpp Ordered fragment reassembly, expiry, cancellation, and fragmentation
include/packet/capture.hpp, src/capture.cpp Capture files, filtering, statistics, sequence validation, and replay
src/main.cpp Command-line interface
fuzz/fuzz_decode.cpp Frame/capture decoding and canonical wire roundtrips
fuzz/fuzz_stream.cpp Streaming input and generated fragment/reassembly properties

The shared CMake helpers live in ../cmake/ProjectOptions.cmake; preserve that relative layout when moving the project.

Wire Format

All fixed-width integers use big-endian byte order. A frame starts with a 24-byte header, followed by a TLV payload:

Offset Size Meaning
0 3 ASCII magic FZP
3 1 Version, currently 1
4 1 Flags
5 1 Reserved; must be zero
6 2 Channel
8 4 Sequence number
12 4 Message ID
16 4 Payload length
20 4 CRC32
24 Variable TLV payload

CRC32 uses the reflected polynomial 0xEDB88320, initial state 0xffffffff, and final complement. Coverage is header bytes 0–19 followed by the payload; the checksum field is excluded.

Flags are 0x01 for fragmentation, 0x02 for the final fragment, and 0x04 for a response. Other flag bits are rejected. Final requires fragmentation.

Each TLV contains a 2-byte type, a 4-byte length, and that many value bytes:

Type Value representation
1: Text Valid UTF-8, including an empty string
2: Number Exactly 8 bytes, unsigned integer
3: Blob Arbitrary bytes
4: Attribute 2-byte key length, nonempty UTF-8 key, UTF-8 value
5: Timestamp Exactly 8 bytes, unsigned integer
6: Boolean Exactly one byte: 0 or 1

Type zero is reserved. Unknown types below 0x8000 are preserved; unknown types with the high bit set are treated as critical and rejected. Field ordering and duplicate fields are preserved. There is no implicit application-level schema.

Fragmentation and Stream Semantics

Messages consist of exactly one Blob field per frame. A fragmented message starts at sequence zero and continues without gaps or duplicates. Reassembly is keyed by (channel, message ID). All fragments must agree on the response flag. The final fragment completes and removes the pending message.

Limits bound individual fields, payloads, pending messages, fragment count, and each reassembled message. Aggregate reassembly memory can reach max_messages * max_message; there is no separate global memory budget. Expiry uses caller-provided monotonically increasing ticks.

StreamDecoder::feed accepts partial or multiple frames. The feed plus buffered data must fit max_buffer, so large files should be fed in chunks. In strict mode, a malformed frame raises an exception. Recovery mode searches for the next FZP alignment and counts discarded bytes and rejected candidates.

Recovery cannot guarantee progress past an apparently valid header advertising a not-yet-complete frame. finish() rejects any trailing incomplete data, including leftover bytes in recovery mode. Reset after a strict-mode failure before reusing a decoder.

Capture Format

A capture begins with ASCII FZC1 and a 4-byte record count. Each record contains an 8-byte timestamp, a 4-byte frame length, and the encoded frame. A trailing 4-byte CRC32 covers everything preceding it.

Capture inspection accepts general frames. Message replay additionally requires the Blob-only message convention and rejects captures ending with incomplete messages. Sequence validation is a separate check from wire-format validation.

Build and CLI

After resolving the missing test-source item in the status section, run from this directory with CMake 3.16+, a C++17 compiler, and Ninja:

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build --output-on-failure

build/packet_cli encode hello.bin 1 42 "hello"
build/packet_cli decode hello.bin
build/packet_cli hex hello.bin
build/packet_cli stream hello.bin
build/packet_cli capture hello.bin hello.fzc
build/packet_cli inspect hello.fzc
build/packet_cli stats hello.fzc
build/packet_cli validate hello.fzc
build/packet_cli filter hello.fzc channel-one.fzc 1

To split an arbitrary input into Blob messages and restore it:

printf 'a small message' > message.txt
build/packet_cli fragment message.txt fragments.fzc 4
build/packet_cli replay fragments.fzc restored.txt

The CLI’s encode command creates a Text frame. Such a frame can be inspected and captured but is not accepted by Blob-message replay. Replay output requires exactly one completed message. Exit status 1 indicates a protocol or I/O error; status 2 indicates invalid command usage.

Static Analysis

mkdir -p reports
clang++ --analyze -std=c++17 -Iinclude src/codec.cpp \
  -Xanalyzer -analyzer-output=plist -o reports/codec.plist
CodeChecker analyze build/compile_commands.json -o reports/codechecker
CodeChecker parse reports/codechecker
CodeChecker analyze build/compile_commands.json --ctu -o reports/ctu

Clang and CodeChecker must be installed separately. CTU requires compatible Clang mapping tools. Analyze bytes.cpp, codec.cpp, stream.cpp, and message.cpp together to explore data flow across translation units. Static diagnostic availability depends on the analyzer and enabled checkers.

Sanitizers and Fuzzing

cmake -S . -B build-fuzz -G Ninja -DCMAKE_CXX_COMPILER=clang++ \
  -DCMAKE_BUILD_TYPE=Debug -DENABLE_FUZZING=ON
cmake --build build-fuzz
mkdir -p corpus/decode corpus/stream artifacts
build-fuzz/packet_cli encode corpus/decode/text.bin 1 1 "hello"
printf 'stream seed' > corpus/stream/message.bin
build-fuzz/packet_fuzz_decode corpus/decode -max_total_time=60 -max_len=65536 \
  -artifact_prefix=artifacts/
build-fuzz/packet_fuzz_stream corpus/stream -max_total_time=60 -max_len=65536 \
  -artifact_prefix=artifacts/

ENABLE_FUZZING requires Clang/libFuzzer and enables ASan/UBSan. For a standalone sanitizer build, use -DENABLE_SANITIZERS=ON in a separate directory. A replay command is build-fuzz/packet_fuzz_decode artifacts/crash-<hash> or the matching stream target. These campaigns have not been run.

Optional Demonstration Defects

Use a separate configuration with both -DENABLE_DEMO_BUGS=ON and -DENABLE_FUZZING=ON. Normal CTest registration is disabled in this configuration.

ID Location Trigger Expected detector Repair
PKT-001 src/codec.cpp, decode_fields Type 0x70 with more than 8 value bytes in a valid checksummed frame ASan; possible static buffer warning Decode directly into bounded dynamic storage
PKT-002 src/message.cpp, Reassembler::accept A fragmented Blob message on channel 0xdead ASan and potentially use-after-free analysis Retain ownership until the final read; remove the stale cache pointer
PKT-003 src/message.cpp, finalization A nonempty completed fragmented message Reassembly equality oracle Do not discard the final byte

PKT-001 can be constructed through the library without manually calculating CRC:

packet::Frame frame;
frame.fields.push_back({0x70, packet::Bytes(32, 'A')});
auto wire = packet::encode(frame);
auto decoded = packet::decode(packet::View(wire));

For PKT-002, create a packet::Message with channel 0xdead, a 64-byte body, and fragment(message, 32), then feed the frames to Reassembler::accept. For PKT-003, use an ordinary channel and compare the reassembled body with the original. The stream Fuzz harness already contains this equality oracle.

printf '3655abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' > pkt-002.bin
printf 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' > pkt-003.bin
build-bugs/packet_fuzz_stream pkt-002.bin
build-bugs/packet_fuzz_stream pkt-003.bin

Run each failure case separately. build-bugs must first be configured and built with the options above. The current draft does not bundle a standalone PKT-001 fixture generator or a completed unit-test suite.

Intended Validation and Limitations

Planned tests include every truncated header length, impossible TLV lengths, checksum changes, unknown critical types, invalid UTF-8, split-point invariance, concatenated frames, out-of-order/duplicate fragments, expiry, capture corruption, and randomized encode/decode and fragment/reassembly roundtrips.

The format is educational and is not compatible with an external network protocol. CRC32 detects accidental corruption; it provides no authentication. The implementation has no sockets, encryption, retransmission, or concurrent access. Final compilation, tests, and the 3000–5000-line source acceptance check remain pending. The shared counter is ../tools/count_loc.py.

关于
48.0 KB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号