Files
ferrix/fuzz
Rayhaan JaufeerallyandClaude Fable 5.1 6197a7ad74 Handle errors in UPDATE messages as RFC 7606 says, not by resetting
Any malformed UPDATE used to reset the session, withdrawing every route
learned over it. One bad attribute, typically originated ASes away and
passed on by a peer that did not look at it, could take down a session
carrying a full table.

UpdateMessage::decode now applies RFC 7606 and returns the UPDATE as it is
to be acted on, plus a report of what was wrong:

- Treat-as-withdraw: a malformed or misflagged attribute, a missing
  mandatory one, or an attribute that overruns the attribute list. The
  routes the UPDATE announced (in the body and in MP_REACH_NLRI) are
  withdrawn.
- Attribute discard: malformed ATOMIC_AGGREGATE, AGGREGATOR, AS4_PATH and
  AS4_AGGREGATOR; an attribute sent twice (the first is kept); LOCAL_PREF,
  ORIGINATOR_ID and CLUSTER_LIST from an external peer; AS4_PATH between
  four-octet speakers.
- Session reset remains where the prefixes are in doubt: the lengths that
  frame the message, an unreadable prefix, a malformed or repeated
  MP_REACH_NLRI or MP_UNREACH_NLRI, an unrecognised well-known attribute.
  Each now sends its own subcode instead of always Malformed Attribute
  List.

The session logs each handled error and counts it in
bgp_update_errors_total{handling}. The parser context says whether the
peer is internal (replacing a field nothing read).

Stricter than before, because the checks did not exist:
- An announcement must carry ORIGIN and AS_PATH, NEXT_HOP for routes in the
  body, and LOCAL_PREF from an internal peer.
- An AS_PATH segment must be an AS_SET or AS_SEQUENCE and not empty.
- An attribute value must be used up entirely by its parser; a NEXT_HOP of
  six octets, say, was accepted before.

Bugs found on the way: we sent ATOMIC_AGGREGATE flagged optional and
AGGREGATOR flagged non-transitive. The flags now come from one table used
for both writing and checking.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-01 23:12:33 +02:00
..

Fuzz targets

Coverage-guided fuzzing of everything that reads input we do not control. The property tests (cargo test, e.g. crates/packet/tests/properties.rs) check the same things on generated input and run on every build; these targets go further by letting libFuzzer follow code coverage for as long as you leave them running.

Target Input
message Raw bytes as they come off a TCP connection
message_body A message body behind a correct header (reaches the body parsers)
prefix_text A prefix as text
config The configuration file

For the two message targets a finding is either a panic, or a message the decoder accepted that cannot be written and read back as itself.

Running

libFuzzer needs a nightly toolchain and cargo-fuzz:

rustup toolchain install nightly
cargo install cargo-fuzz

Then, from this directory:

cargo +nightly fuzz run message_body corpus/message_body seeds/message_body -- -max_total_time=300

The first directory is the working corpus, which the fuzzer adds to and which is not checked in. seeds/ holds a few valid inputs to start from. Leave out -max_total_time to run until interrupted.

When it finds something

The input is saved under artifacts/<target>/. Reproduce it with

cargo +nightly fuzz run message_body artifacts/message_body/crash-...

Fix the bug, and add a unit test with the input next to the code it broke, so that cargo test keeps it fixed without anyone having to run the fuzzer.