Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
andrewrk opened this issue Jul 26, 2024 · 2 comments · May be fixed by #21246
Open

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

andrewrk opened this issue Jul 26, 2024 · 2 comments · May be fixed by #21246
Labels
enhancement Solving this issue will likely involve adding new logic or components to the codebase. fuzzing
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Jul 26, 2024

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.

@andrewrk andrewrk added enhancement Solving this issue will likely involve adding new logic or components to the codebase. fuzzing labels Jul 26, 2024
@andrewrk andrewrk added this to the 0.14.0 milestone Jul 26, 2024
@andrewrk
Copy link
Member Author

andrewrk commented Aug 8, 2024

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.

@gcoakes
Copy link
Contributor

gcoakes commented Aug 11, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Solving this issue will likely involve adding new logic or components to the codebase. fuzzing
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants