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

Feature: Allow target to abort/cancel current test case #655

Open
Manouchehri opened this issue Dec 25, 2020 · 4 comments
Open

Feature: Allow target to abort/cancel current test case #655

Manouchehri opened this issue Dec 25, 2020 · 4 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@Manouchehri
Copy link
Contributor

Setup

I'm currently taking advantage of the extremely fast speed of persistent qemu_mode to avoid forking or needing to reset the memory state (along with afl_persistent_hook to avoid I/O). (i.e. I only need to set AFL_QEMU_PERSISTENT_ADDR=0x1337aaaa.)

I'm fuzzing closed source libraries, and sometimes I do run into weird edge that require resetting the entire state (the most common being memory leaks). I have written a crash handler in my target itself that detects some of these edge cases (e.g. if OOM, just exit(0);).

Problem

The bigger issue is that AFL++ sees that it "found" a new path to my crash handler, and considers it to be a unique (A -> B -> C -> OOM handler is technically different than A -> B -> OOM handler). This usually isn't even reproducible, as running one small leaky test case by itself isn't going to cause an OOM crash.

Example Situation

  • Persistent iterations 1 to 5,000 run "fine", and leak a small amount of memory on each iteration (this is fine for now as I have enough memory)
  • Persistent iterations 5,000 to 9,998 run without issue
  • Persistent iteration 9,999 leaks a tons of memory, but does not quite crash
  • Persistent iteration 10,000 "crashes" on a OOM, because the previous iteration basically left us with no memory to use.

Possible Solutions

  1. Use AFL_QEMU_PERSISTENT_MEM. This far from ideal, as in the example above I'd be unnecessarily wasting time resetting the memory state for problem that's only ruining ~0.01% of iterations. (My real world targets are well under <1% too, this isn't a theoretical situation.)
  2. Do nothing and spend time pruning the invalid crashes after (this is my "current" solution and it's getting annoying).
  3. Add new flags like AFL_VERIFY_CRASH and AFL_VERIFY_NEW_TESTCASE that would require AFL++ to rerun any testcases in a fresh state before saving or adding them to the queue.
  4. Have the target raise an unused/"special" signal code (i.e. 32) that will cause AFL++ to consider the current test case iteration to be invalid and to restart.
  5. Have the target use an unused/"special" exit code (i.e. 64) that will cause AFL++ to consider the current test case iteration to be invalid and to restart.
  6. Implement a shared map that the target can use to notify AFL++ to consider the current test case iteration to be invalid and to restart.

Previous Discussions

I had asked in the Awesome Fuzzing Discord for advice back in November. Decided I should put here on GitHub so I don't lose track of it myself. =)

Based on the replies, I think we/I should implement solution 6?

Well the whole feedback reporting is using a shared map, you may be able to take some ideas from that, but the main communication with the forkserver is pipe-based. We try to not break the old protocol (afl++ targets and afl-fuzz are compatible, and vice versa)

yes this information should be transferred via the pipe.
not all afl++ targets are directly compatible with afl due to non-colliding. in that case config.h MAP_SIZE_POW2 has to be increased to match in afl variants

Questions

Any additional advice or warnings before I attempt this? 😃

@domenukk
Copy link
Member

This would be nice to have, indeed.
Actually, the target's forkserver already sends over the child's exit code, see:

if (read(fsrv->fsrv_st_fd, &fsrv->child_status, 4) < 4) { exec_ms = 0; }

In unicorn mode, I get the correct id to report using this function:
https://github.com/AFLplusplus/unicornafl/blob/768e6bb29b7cb98bb2b9c4526ae3d234db5c1615/afl-unicorn-cpu-inl.h#L157
Something like this should already work in qemu mode. However, there is likely a better way to get the "right" signal id, this function was merely a quick hack :)

@vanhauser-thc
Copy link
Member

I agree that the forkserver communicationis where this should be implemented. there should be a few bits unused.

@vanhauser-thc
Copy link
Member

btw. easiest would be not to instrument the crash handler.

echo fun: mycrashhandler > block.txt
export AFL_LLVM_DENYLIST=`pwd`/block.txt
CC=afl-clang-fast make

@vanhauser-thc vanhauser-thc added enhancement New feature or request help wanted Extra attention is needed labels Jan 19, 2021
@kotee4ko
Copy link

kotee4ko commented Aug 13, 2022

You can use signals.
AFL abort current queue if catch SIGUSR1.
Also, you can look how timedout queues are handled in AFL side.

Other way is reporting target IP at the moment of crash to AFL via pipe.
Adding to queue structure two new fields like 'crashed_times' and 'crashed_ip' should solve the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants