Fuzzsight offloads coverage-guided fuzzing's edge-tracking to hardware: instead of instrumenting the target binary, it uses the CoreSight ETM trace already built into a Zynq UltraScale+'s Cortex-A53 cores (ZCU104 here) to observe every taken/not-taken branch for free, and turns that trace into an AFL-style coverage bitmap entirely in the PL, with zero target-side overhead.
The pipeline: an existing CoreSight trace decoder (rtl/coresight-trace-decoder, forked and extended from the ETH-Zurich) demuxes/deformats/decodes the raw trace byte stream into taken/not-taken atoms; edge_extractor (rtl/fuzzsight) turns those atoms into AFL-style edge-coverage indexes, with two collision-avoiding hashing schemes selectable at runtime (STALKER and this project's own FUZZSIGHT, see rtl/fuzzsight/hash_pkg.vhd); bitmap_writer/bitmap_reader maintain the coverage bitmap in BRAM and hand it off to the PS over AXI-DMA between fuzzing iterations. rtl/stats adds decoder/edge statistics for profiling the pipeline itself.
This repo covers the RTL, the Vivado build (via the vivgit submodule in scripts/), and the userspace C "drivers" (src/) for the AXI-Lite control interfaces below. The host-side tooling lives in the separate ultrasight project.
The following block designs/projects are available:
fuzzsight: historical setup, with one bitmap writer, superseeded byfuzzsight_trifuzzsight_tri: triplicated writer to handle the packet loadfuzzsight_tri_core3: same asfuzzsight_triwith the decoder and logic extracted from activity on core 3 rather than 0.fuzzsight_stats: only the stats gathering module are present here after the decoder, no edge extraction or bitmap generation
The whole block design is defined in bd/fuzzsight_tri.tcl. Running the synthesis/implementation is done with:
$ make PROJECT=fuzzsight_tri project
$ make N_JOBS=16 PROJECT=fuzzsight_tri synth
$ make N_JOBS=16 IMPL_STRATEGY=Performance_ExplorePostRoutePhysOpt PROJECT=fuzzsight_tri implThis generates the bitstream in build/fuzzsight/fuzzsight.bin. To generate the corresponding device tree overlay, run the command below and double-check that your addresses correspond to the ones in Vivado's address editor:
$ make PROJECT=fuzzsight_tri overlay
$ make PROJECT=fuzzsight_tri dtboOnce a .bin/.dtbo pair exists, stage it to the board and load it from there:
$ make PROJECT=fuzzsight_tri BOARD_DEST=/path/on/board [BOARD_HOST=user@board] deployThis copies build/fuzzsight_tri/{fuzzsight_tri.bin,fuzzsight_tri.dtbo} and install_firmware.sh into $BOARD_DEST/fuzzsight_tri/ locally, or over ssh/scp if BOARD_HOST is set. BOARD_DEST only needs to be writable by the staging user. The actual load needs root, so it always happens by hand on the board:
$ cd /path/on/board/fuzzsight_tri
$ sh install_firmware.sh
Clearing overlay...
Copying raw bitstream (fuzzsight_tri.bin) and dt overlay (fuzzsight_tri.dtbo)...
Loading both...
Time taken to load BIN is 563.000000 Milli Seconds
BIN FILE loaded through FPGA manager successfullyinstall_firmware.sh clears any currently-loaded overlay (fpgautil -R), copies both files into /lib/firmware, then loads the bitstream with its overlay (fpgautil -b ... -o ...).
Before Fuzzsight can push bitmaps to the PS, the
udmabufkernel module needs to be loaded on the board (see ultrasight'sudmabufsetup),bitmap_dma.hmmaps it as the DMA destination.
As soon as CoreSight is enabled (using csal), Fuzzsight fires up and writes edges in a bitmap stored in BRAM. Once the process finishes, the PS is expected to notify the bitmap reader using its AXI-Lite interface, which will read the bitmap and turn into an AXI-Stream, in turn passed to AXI-DMA.
The main logic is defined in bitmap_dma.c:
int bitmap_dma_transfer(bitmap_dma_t *h)
{
// Trigger bitmap reader DMA
axi_regs_write(&h->reader, BITMAP_READER_CTRL, 1);
// Arm S2MM by writting the buffer length, triggering the transfer
axi_regs_write(&h->dma, S2MM_BUFF_LENGTH_REGISTER, h->buf_size);
// Wait for bitmap reader to confirm done
while (!(axi_regs_read(&h->reader, BITMAP_READER_STATUS) & STATUS_DMA_DONE))
;
// Wait for S2MM to complete
uint32_t status;
do {
status = axi_regs_read(&h->dma, S2MM_STATUS_REGISTER);
} while (!(status & STATUS_IOC_IRQ) || !(status & STATUS_IDLE));
// Rearm S2MM for next transfer
axi_regs_write(&h->dma, S2MM_DST_ADDRESS_REGISTER, UDMABUF_DST);
axi_regs_write(&h->dma, S2MM_CONTROL_REGISTER, RUN_DMA | ENABLE_ALL_IRQ);
return 0;
}For more information on this part, look at
ultrasightand its AFL++ proxycs-proxy.c.
Every PL block that needs runtime control exposes a small AXI-Lite register file, and each gets a thin C "driver" header in src/, all built on the same base: axi_regs.h/axi_regs.c maps a block's physical address range through /dev/mem and provides raw axi_regs_open/_close/_read/_write/_print. Every header below is just a typed register map plus inline wrappers over these.
| Header | Block | Base macro | What it configures / reports |
|---|---|---|---|
decoder_axi.h |
CoreSight trace decoder pipeline | DECODER_AXI_BASE |
Soft reset + error-count reset (CTRL), reset-done (STATUS), frame/bytestream-gen/demux/overflow error counters, and the 64-bit trace address range filter (decoder_axi_set_range()) |
decoder_stats.h |
Decoder profiling counters (rtl/stats) |
DEC_STATS_BASE_ETM |
Enable/disable, total/idle cycles, packet and atom-packet counts with burst/gap histograms, atom-element throughput -- dec_stats_print_detailed() renders all of it |
edge_extractor.h |
edge_extractor |
EDGE_EXTRACTOR_BASE |
Stats reset; runtime hash-mode select (edge_extractor_set_hash_mode()/_get_hash_mode() -- NONE/FUZZSIGHT/STALKER, see hash_mode_plan.md); edges-total/FIFO-overflow/freeze-drop counters |
bitmap_dma.h/.c |
bitmap_reader_bram + AXI-DMA (S2MM) + udmabuf |
BITMAP_READER_BASE, DMA_BASE |
Bundles the reader's trigger/status AXI-Lite, the DMA's S2MM control block, and the mmap'd udmabuf destination behind one handle; bitmap_dma_transfer() drives the full reader-drains -> DMA-arms -> DMA-completes -> re-arm handshake |
Every base-address macro above is a placeholder (with a build-time #pragma message warning) until overridden with your own Vivado address map -- pass it as a -D define when building whatever consumes these headers, e.g. -DEDGE_EXTRACTOR_BASE=0x80021000 (ultrasight's own copies already bake in this repo's real ZCU104 addresses instead).