Open
Conversation
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
c41ea69 to
fa1e792
Compare
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.
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:
parse_bytes()(numeric,hex, IPv4, IPv6, MAC address parsing)
argc_frame()(all header and field parsers)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.