Skip to content

write fuzz inputs to a shared memory region before running a task #20803

Description

@andrewrk

Extracted from #20773.

Currently, a fuzz test failure looks like this:

andy@bark ~/t/abc> zig build test --fuzz 
test
└─ run test failure
/home/andy/local/lib/zig/std/testing.zig:546:14: 0x11575c9 in expect (test)
    if (!ok) return error.TestUnexpectedResult;
             ^
/home/andy/tmp/abc/src/main.zig:28:5: 0x1157691 in test.fuzz example (test)
    try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input_bytes));
    ^
failed with error.TestUnexpectedResult
error: the following command exited with error code 1:
/home/andy/tmp/abc/.zig-cache/o/eea1979fed4d51bc1ca0d161af979e22/test --seed=0x48fe2aeb --listen=- 
error: all fuzz workers crashed
error: the following build command failed with exit code 1:
/home/andy/tmp/abc/.zig-cache/o/bc5565bbec3a56db01acb2ab6b348742/build /home/andy/local/bin/zig /home/andy/local/lib/zig /home/andy/tmp/abc /home/andy/tmp/abc/.zig-cache /home/andy/.cache/zig --seed 0x48fe2aeb -Zb2ede88d1c7627c9 test --fuzz

If you rerun that command that it printed, it does not in fact reproduce the issue:

andy@bark ~/t/abc [1]> /home/andy/tmp/abc/.zig-cache/o/eea1979fed4d51bc1ca0d161af979e22/test --seed=0x48fe2aeb
All 2 tests passed.
1 fuzz tests found.

This is due to lack of communication between parent process (build runner) and fuzzing process (test runner).

However, for performance purposes, we don't want any communication between those processes in the hot path. That means we cannot send a message containing the current input before trying it.

Options are:

Follow the lead from other fuzzers by having a "corpus" directory, which is a list of files memory mapped into the fuzzer process, one per "interesting" input, with filenames corresponding to the run IDs. Advantages to this approach is that it's easy to recover and it could be used to share state across processes. Disadvantage is that it writes to the filesystem in a hot path. Maybe that's OK in practice? I'll have to check.

Another idea that I had is to have the parent process (build runner) create and share a memory mapping with the fuzzing process (test runner). The fuzzer would use this memory to store its most recent input(s) as well as some metadata (for example stats to display on the UI). The parent process can then read from this shared mapping to display the stats in real time as well as to recover inputs when the fuzzer process crashes.

It might not be such a bad idea to send a message when an "interesting" input is found. This message would perhaps be forwarded to other fuzzing processes, perhaps on the same system or perhaps even on other systems. Then again, using a file system directory as a "corpus" directory would also allow other processes, including peers and parents, to notice and pick up interesting inputs.

This issue is a tad bit open ended, but at least to close it, interesting inputs that are found should be displayed in a reproducible manner, where re-running a particular command will in fact reproduce the crash.

Activity

  1. added
    enhancementSolving this issue will likely involve adding new logic or components to the codebase.
    on Jul 26, 2024
  2. added this to the 0.14.0 milestone on Jul 26, 2024
  3. andrewrk commented on Aug 8, 2024

    @andrewrk
    MemberAuthor

    I'm thinking the next step here is to use .zig-cache/f for corpus directory, keyed on the fully-qualified unit test names, and then implement AFL's strategy of maintaining a minimal set of inputs that trigger unique execution paths as memory-mapped files in this directory. At some point users may then decide to minimize the inputs and then copy them into the source tree, switching over to providing them via std.testing API.

  4. gcoakes commented on Aug 11, 2024

    @gcoakes
    Contributor

    fuzzer would use this memory to store its most recent input(s)

    The current implementation as of today has a shared, memory-mapped file at .zig-cache/v/<program_counter_digest>. I don't think that is the appropriate place to map the current input since that would prohibit parallel processes from fuzzing the same set of program counters. Also, there is currently an assumption that a single test function will be fuzzed within a given test process. Would it be a good idea for each process to use a shared, memory-mapped file as the backing for Fuzzer.input located at .zig-cache/f/<test_fqn>/<pid>. It could be renamed by the parent process when a crash occurs, or it could be renamed by the fuzzing process when an error occurs.

    keyed on the fully-qualified unit test names

    Does fully-qualified additionally include a build ID for that build of the test? Or, would you want subsequent builds to retain the same cached "interesting" inputs? If the latter, I think we would need to add a phase in which it reanalyzes the cached inputs according to the current program counters.

  5. modified the milestones: 0.14.0, 0.15.0 on Feb 10, 2025
  6. added 3 commits that reference this issue on Feb 11, 2025
    71d96c9
    b3f6af0
    d789f1e
  7. andrewrk commented on Feb 12, 2025

    @andrewrk
    MemberAuthor

    commit message says

    does not quite close #20803 yet

  8. added 4 commits that reference this issue on Mar 31, 2025
    69dbdec
    3314906
    cbcbb9a
    45e6f5b
  9. added 2 commits that reference this issue on May 1, 2025
    d5a4147
    05547ca
  10. added 2 commits that reference this issue on Jul 10, 2025
    f91f498
    ffe72af
  11. Atomk commented on Sep 21, 2025

    @Atomk
    Contributor

    This was closed (perhaps automatically) when merging #23416 but that PR's description says:

    this does not close #20803 since it does not output the input

    Is this intentional?

  12. reopened this on Sep 21, 2025
  13. andrewrk commented on Apr 3, 2026

    @andrewrk
    MemberAuthor
  14. modified the milestones: urgent, 0.16.0 on Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementSolving this issue will likely involve adding new logic or components to the codebase.fuzzing

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions