Skip to content

Add fuzzing to easyframes with LibFuzzer#15

Open
jeso-mchp wants to merge 9 commits intomasterfrom
master.fuzzing
Open

Add fuzzing to easyframes with LibFuzzer#15
jeso-mchp wants to merge 9 commits intomasterfrom
master.fuzzing

Conversation

@jeso-mchp
Copy link
Copy Markdown
Contributor

LibFuzzer is part of LLVM, so it is readily available when you have llvm installed. It is not as advanced as other fuzzers, but it is very easy to setup.

This addsa three libFuzzer harnesses exercise ef's parsing logic with AddressSanitizer enabled:

  • fuzz-parse-bytes: feeds random strings to parse_bytes() (numeric,
    hex, IPv4, IPv6, MAC address parsing)
  • fuzz-argc-frame: splits input on null bytes into argv and calls
    argc_frame() (all header and field parsers)
  • fuzz-roundtrip: property-based test: parses a frame, serializes it,
    and asserts that the result matches its own receive filter via
    bequal_mask(). Uses the same input format and corpus as fuzz-argc-frame.

The new targets are guarded behind a FUZZ_ENABLE=on cmake flag. The README explains how to run the fuzzer. Included are a bunch of fixes to bugs found by the fuzzers.

Three fuzz harnesses with ASan+libFuzzer:
- fuzz-parse-bytes: exercises parse_bytes() with arbitrary input
- fuzz-argc-frame: exercises argc_frame() with random token arrays
- fuzz-roundtrip: property-based test that frame_to_buf(parse(frame))
  produces deterministic output, including padded frames

Includes fuzz dictionary and CMake FUZZ_ENABLE option.
When the hex or binary data has more bytes than the output field size,
the write pointer is calculated as b->data + size - data_len, which
underflows and writes before the allocated buffer.

Reject input where the parsed data exceeds the field size instead of
blindly computing a negative offset.

Found by libFuzzer with AddressSanitizer: input "0x88a83" followed by
78 'A' characters produces 42 bytes of hex data for a 16-byte field,
causing a write 26 bytes before the buffer.
When parsing fails mid-way through multiple commands, the
already-parsed commands (frames, buffers, strdup'd names) were
never freed. Route error paths through a cleanup label that
calls cmd_destruct on all parsed commands before returning.

Found with AddressSanitizer LeakSanitizer on:
  ./ef name f eth tx eth_red
mld_fill_defaults called exit() when MLD was used without a
preceding IPv6 header, killing the entire process. Return -1
instead so frame_to_buf can propagate the error to callers.

Check frame_to_buf return value in argc_cmd so invalid
combinations produce a proper error exit rather than silently
succeeding.
…ults

Found by fuzz-roundtrip with input: eth ign ipv6 sv

  Assertion `8 * b->size >= (size_t)f->bit_width + f->bit_offset + offset * 8'
  failed in hdr_write_field, called from frame_mask_to_buf → hdr_copy_to_buf_mask.

When no TLV2 fields are specified, sv_fill_defaults shrinks the SV header
from 22 to 14 bytes and zeros TLV2 bit_widths.  But it did not adjust the
bit_offsets of the TLV0 fields (tlv0_type, tlv0_len) that follow TLV2 in
the field array.  They kept their original offsets at bits 160 and 168
(bytes 20-21), well past the shrunken 14-byte header.

In frame_to_buf this was harmless because TLV0 has no default values, so
hdr_write_field was never called for those fields.  But frame_mask_to_buf
creates mask values for all non-ignored fields, triggering the assertion
when it tried to write at bit 160 in a 14-byte header.

Reproducible outside fuzzing: ./ef hex eth ign ipv6 sv

Fix: shift TLV0 bit_offsets down by 64 bits (the 8 bytes of TLV2) when
TLV2 is removed.
…zero

Two bugs in bequal_mask when padding > 0:

1. Heap buffer over-read: the comparison loop ran up to rx_frame->size,
   but expected_frame only has expected_frame->size bytes allocated.
   Indices beyond that read unallocated heap memory.  Affects both the
   memcmp path (no mask) and the masked byte loop.

2. Padding bytes were never validated.  Ethernet padding is zero-filled,
   so non-zero padding bytes indicate a corrupted or wrong frame.

Fix: compare only up to expected_frame->size, then verify trailing
padding bytes are all zero.

Add 8 unit tests covering padding matching, size checks, masked
padding, zero-fill verification, and negative padding rejection.
The padding parser accepted any uint32 value, allowing nonsensical
values like 0x6eadbef6 (~1.8GB) that cause OOM.  Cap at 0xffff
(65535), the maximum IP packet size.

Also cap the fuzz harness to match, guarding against fuzz-generated
values that bypass normal CLI parsing.
Fuzzer crash: eth ign ipv6 ign ipv6 igmp
  crash-f86f0f4954bca7d9c3ddc000504958de1c4b6346

Several headers (IGMP, MLD, SV, CoAP) shrink during frame_fill_defaults
by zeroing bit_width of removed fields. However their bit_offset values
remain at the original (pre-shrink) positions. When frame_mask_to_buf
later iterates all fields to build the receive mask, it creates a
zero-size mask value for these zero-width fields and calls
hdr_write_field, which asserts that bit_offset + bit_width fits within
the buffer — but the stale bit_offset points past the shrunken header.

For IGMP without v3 query/report fields: h->size shrinks by 8 bytes
(from 16 to 8), but fields like rresv (bit_offset=96) and ng
(bit_offset=112) keep their original offsets, causing the assertion
to fire when the frame buffer is too small to contain those offsets.

Fix: skip fields with bit_width == 0 in hdr_copy_to_buf_. A zero-width
field has no bits to serialize or mask, so processing it is always a
no-op — except for the assertion check, which sees the stale offset
and aborts. This is a systemic fix covering all current and future
headers that shrink during fill_defaults, rather than patching each
fill_defaults individually.
coap_parse_parms declared buf_t *b without initializing to NULL.
When parse_var_bytes returned 0 (e.g. empty input), it did not write
to its output parameter, leaving b as a garbage pointer. The
subsequent bfree(b) freed an arbitrary address.

Initialize b to 0 so bfree is a safe no-op on the error path.

Found by libFuzzer with AddressSanitizer:
      ef hex coap-parms par
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant